Why Is It Disallowed for Partial Specialization in a Non-Type Argument to Use Nested Template Parameters

Why is it disallowed for partial specialization in a non-type argument to use nested template parameters

I think a lot of it is historical. Non-type template parameters weren't originally allowed at all. When they were added, there were lots of limitations. As people tried different possibilities, and confirmed that they didn't cause problems, some of the limitations were removed.

Some of those original limitations remain for no particular reason beyond the fact that nobody bothered to work at changing them. Much like there, many of them can be worked around so removing them generally often wouldn't cause any particular difficulty. Mostly it comes down to a question of whether anybody cared enough about this particular case to write a paper about it.

(Partially) specializing a non-type template parameter of dependent type

See paragraph [temp.class.spec] 14.5.5/8 of the standard:

The type of a template parameter corresponding to a specialized
non-type argument shall not be dependent on a parameter of the
specialization. [ Example:

template <class T, T t> struct C {};
template <class T> struct C<T, 1>; // error

template< int X, int (*array_ptr)[X] > class A {};
int array[5];
template< int X > class A<X,&array> { }; // error

—end example ]

The answer to your edit: the easiest workaround is to replace a non-type template parameter with a type one:

#include <type_traits>

template <typename T, typename U>
struct X_;

template <typename T, T N>
struct X_<T, std::integral_constant<T, N>> {};

template <typename T>
struct X_<T, std::integral_constant<T, 0>> {};

template <typename T, T N>
struct X : X_<T, std::integral_constant<T, N>> {};

C++ template specializations not working with nested types

Your nested alias could refer to any type, particularly in a specialisation:

template<typename T>
struct Nesting
{
template<typename U>
struct _Nested
{
};

template<typename U>
using Nested = _Nested<U>;
};

// Consider this specialisation:
template<>
struct Nesting<int>
{
template<typename U>
using Nested = float;
};

Now, clearly F<Nesting<int>::Nested<int>>::is_my_nested_class should be the same as F<float>::is_my_nested_class, however, how can the compiler deduce this for the latter case? That is, if I wrote:

static_assert(F<float>::is_my_nested_class, "not nested");

The compiler would need to see that F<float> is the same as F<Nesting<int>::Nested<int>>, even though the latter hasn't been instantiated. As it can't reasonably be expected to do so, the case is disallowed.

Why is partial specialization of a nested class template allowed, while complete isn't?

My guess as to why this happens: complete specializations are no longer "template classes/functions", they are are "real" classes/methods, and get to have real (linker-visible) symbols. But for a completely-specialized template inside a partially-specialized one, this would not be true.
Probably this decision was taken just to simplify the life of compiler-writers (and make life harder for coders, in the process :P ).

Split variadic parameter pack using template specialization

What exactly is going wrong, [...]

There are several issues so it may be easier to start with a simple base example that works (no perfect forwarding).

[...] and how might I build my map primitive in a way that avoid this?

You can separate the parameter packs:

  • argument types are passed as template arguments for Map
  • item types are passed as template arguments for Map::function

Here is a working example without perfect forwarding. It is not complete regarding the deduction of the argument types because of possible cv-qualifiers and ref-qualifiers.

#include <iostream>

template<class F, class... Args>
struct Map {
template<class... Items>
static void function(Args... args, Items... items) {
static constexpr auto f = F{};

// here comes a fold expression
// see https://en.cppreference.com/w/cpp/language/fold

( f(items, args...), ...); // "fold over comma operator"
}
};

////////////////////////////////////////////////////////////////////////////////

template<class F, class Ret, class Item, class... Args, class... ArgsItems>
void map_impl(Ret(F::*)(Item, Args...) const, ArgsItems... args_items) {
Map<F, Args...>::function(args_items...);
}

template<class F, class... ArgsItems>
void map(ArgsItems... args_items) {
map_impl<F>(&F::operator(), args_items...);
}

////////////////////////////////////////////////////////////////////////////////

struct print_x_m_plus_n {
void operator()(int x, int m, int n) const {
int y = x * m + n;
std::cout << y << std::endl;
}
};

int main() {
constexpr int m = 2;
constexpr int n = 1;

map<print_x_m_plus_n>(m, n, 0, 1, 2);
}

Output:

1
3
5

SFINAE tried with bool gives compiler error: template argument ‘T::value’ involves template parameter

Actually what you're doing is forbidden by section §14.5.4/9 which says,

A partially specialized non-type argument expression shall not involve a template parameter of the partial specialization except when the argument expression is a simple identifier.

The trick could be using a type for second template parameter as well, encapsulating the non-type value, as described below:

template<bool b> struct booltype {};

template<typename T, typename B = booltype<true> >
struct Resolve
{
static const bool value = false;
};

template<typename T>
struct Resolve<T, booltype<T::my_value> >
{
static const bool value = true;
};

Now it compile fines.

partial specialization for iterator type of a specified container type

You can't deduce types left of a nesting ::. Indeed, your question makes no sense. Consider this simpler counter-example:

template <typename> struct Foo;
template <> struct Foo<bool> { typedef float type; };
template <> struct Foo<char> { typedef float type; };

template <typename> struct DoesntWork;

template <typename T> struct DoesntWork<typename Foo<T>::type> { };

Now if I say DoesntWork<float>, what should T be?

The point is that there is no reason that any T should exist for which Foo<T>::type is a thing you want to match, and even if there were one, there's no reason why it would be unique.

Template argument specialization example from cpprefference.com doesn't work

This is a recent language change, and even the current releases of several compilers don't implement it yet. It's CWG issue 1315, which lists the status as "tentatively ready", though according to @bogdan in the comments, the change has already been accepted into the standard. Prior to that change, it was invalid for exactly the reason that your compiler shows in its error message.

Changing GCC's behaviour is on the GCC bug tracker as PR 77781.

c++, can I enable several trait class specializations using enable_if instead of copy-paste?

I find very difficult to understand what do you want; please, revise you question to check details (version and revision are the same? fieldTraits and traitsClass are the same?).

Anyway, if I understand correctly, you want define a specialization for

template <const field& T, revision R>
class fieldTraits;

when R is val1 or val2 or val3 and, maybe, other specializations for single following values.

Supposing you have four revisions

enum class revision { ver1, ver2, ver3, ver4 };

You can declare fieldTraits (as a struct, to make it shorter) adding a bool default template parameter that say if R <= revision::ver3

template <field const & T, revision R, bool = (R <= revision::ver3)>
struct fieldTraits;

Now you can develop a specialization for ver1, ver2 and ver3

template <field const & T, revision R>
struct fieldTraits<T, R, true>
{ static constexpr size_t field_val = 1; };

and a specialization for ver4

template <field const & T>
struct fieldTraits<T, revision::ver4>
{ static constexpr size_t field_val = 2; };

The following is a simplified but full example

#include <iostream>

enum class revision { ver1, ver2, ver3, ver4 };

struct field
{
int thingone;

constexpr field (int i) : thingone(i)
{ }
};

template <field const & T, revision R, bool = (R <= revision::ver3)>
struct fieldTraits;

// version ver1, ver2, ver3 cases
template <field const & T, revision R>
struct fieldTraits<T, R, true>
{ static constexpr size_t field_val = 1; };

// version ver4 case
template <field const & T>
struct fieldTraits<T, revision::ver4>
{ static constexpr size_t field_val = 2; };

static constexpr field f{1};

int main ()
{
std::cout << "ver1: " << fieldTraits<f, revision::ver1>::field_val
<< std::endl;
std::cout << "ver2: " << fieldTraits<f, revision::ver2>::field_val
<< std::endl;
std::cout << "ver3: " << fieldTraits<f, revision::ver3>::field_val
<< std::endl;
std::cout << "ver4: " << fieldTraits<f, revision::ver4>::field_val
<< std::endl;
}

that print

ver1: 1
ver2: 1
ver3: 1
ver4: 2

-- EDIT --

Following an OP's comment, I propose a little different solution

template <field const & T, revision R, typename = std::true_type>
struct fieldTraits;

// version ver1, ver2, ver3 cases
template <field const & T, revision R>
struct fieldTraits<T, R, std::integral_constant<bool, (R <= revision::ver3)>>
{ static constexpr size_t field_val = 1; };

// version ver4 case
template <field const & T>
struct fieldTraits<T, revision::ver4>
{ static constexpr size_t field_val = 2; };


Related Topics



Leave a reply



Submit