Are multiple class template specialisations valid, when each is distinct only between patterns involving template parameters in non-deduced contexts?
A common example of std::void_t uses it to define a trait which reveals whether a type has a member typedef called "type". Here, a single specialisation is employed. This could be extended to identify say whether a type has either a member typedef called "type1", or one called "type2". The C++1z code below compiles with GCC, but not Clang. Is it legal?
template <class, class = std::void_t<>>
struct has_members : std::false_type {};
template <class T>
struct has_members<T, std::void_t<typename T::type1>> : std::true_type {};
template <class T>
struct has_members<T, std::void_t<typename T::type2>> : std::true_type {};
There is a rule that partial specializations have to be more specialized than the primary template - both of your specializations follow that rule. But there isn't a rule that states that partial specializations can never be ambiguous. It's more that - if instantiation leads to ambiguous specialization, the program is ill-formed. But that ambiguous instantiation has to happen first!
It appears that clang is suffering from CWG 1558 here and is overly eager about substituting in void for std::void_t.
This is CWG 1980 almost exactly:
In an example like
template<typename T, typename U> using X = T;
template<typename T> X<void, typename T::type> f();
template<typename T> X<void, typename T::other> f();
it appears that the second declaration of f is a redeclaration of the first but distinguishable by SFINAE, i.e., equivalent but not functionally equivalent.
If you use the non-alias implementation of void_t:
template <class... Ts> struct make_void { using type = void; };
template <class... Ts> using void_t = typename make_void<Ts...>::type;
then clang allows the two different specializations. Sure, instantiating has_members on a type that has both type1 and type2 typedefs errors, but that's expected.
I don't believe it's correct, or at least, not if we instantiate has_members with a type that has both type1 and type2 nested, the result would be two specializations that are
has_members<T, void>
which would not be valid. Until the code is instantiated I think it's ok, but clang is rejecting it early. On g++, your fails with this use-case, once instantiated:
struct X
{
using type1 = int;
using type2 = double;
};
int main() {
has_members<X>::value;
}
The error message is doesn't seem to describe the actual problem, but it at least is emitted:
<source>:20:21: error: incomplete type 'has_members<X>' used in nested name specifier
has_members<X>::value;
^~~~~
If you instantiate it with a type that has only type1 or type2 but not both,
then g++ compiles it cleanly. So it's objecting to the fact that the members are both present, causing conflicting instantiations of the template.
To get the disjunction, I think you'd want code like this:
template <class, class = std::void_t<>>
struct has_members : std::bool_constant<false> {};
template <class T>
struct has_members<T, std::enable_if_t<
std::disjunction<has_member_type1<T>, has_member_type2<T>>::value>> :
std::bool_constant<true> {};
This assumes you have traits to determine has_member_type1 and has_member_type2 already written.
Related
#include <concepts>
#include <tuple>
template<std::integral... Is>
using arithmetic_tuple = std::tuple<Is...>;
template<typename T, template<typename...> typename U>
struct is_instance_of : std::false_type {};
template<typename... Ts, template<typename...> typename U>
struct is_instance_of<U<Ts...>, U> : std::true_type {};
template<typename T>
struct is_tuple : std::false_type {};
template<typename... Ts>
struct is_tuple<std::tuple<Ts...>> : std::true_type {};
template<typename T>
struct is_arithmetic_tuple : std::false_type {};
template<typename... Ts>
struct is_arithmetic_tuple<arithmetic_tuple<Ts...>> : std::true_type {};
static_assert(is_tuple<arithmetic_tuple<int>>::value); // OK
static_assert(is_arithmetic_tuple<arithmetic_tuple<int>>::value); // OK
static_assert(is_instance_of<arithmetic_tuple<int>, arithmetic_tuple>::value); // fails
int main() {}
You can run test here: https://coliru.stacked-crooked.com/a/ce8ae52a1d4918ba
I have write a meta function is_instance_of to check a type is a instantiated type of class template.
And, I have defined a arithmetic_tuple with std::integral constraints.
is_instance_of<arithmetic_tuple<int>, arithmetic_tuple>::value is fales.
However, with more specific meta function is_tuple<arithmetic_tuple<int>>::value and is_arithmetic_tuple<arithmetic_tuple<int>>::value are true.
At a glance, at least is_arithmetic_tuple<arithmetic_tuple<int>>::value should be identical with is_instance_of<arithmetic_tuple<int>, arithmetic_tuple>::value semantically.
Why does is_instance_of test here fail?
And, Can I make it works without more specific type test such as is_tuple?
You can't deduce against an alias template. An alias template, even if written in the form you have, does not given an alias to a template itself. It is instead itself an independent template with each specialization being an alias for a type and these individual type aliases need not be in any way related through e.g. the same class template.
When the compiler comes to the point of choosing the partial specialization for is_instance_of in is_instance_of<arithmetic_tuple<int>, arithmetic_tuple>, the alias arithmetic_tuple<int> has already been resolved to the type std::tuple<int>.
Then when looking at your partial specialization which has U as the alias template arithmetic_tuple the compiler has no way of figuring out which potential Ts... would result in arithmetic_tuple<Ts...> being the same type as std::tuple<int>, because an alias template can map any list of template arguments to any type completely arbitrary. So such a deduction is not allowed. The partial specialization is considered to not match.
If you use
is_instance_of<arithmetic_tuple<int>, std::tuple>
instead, it will work as you expect, because std::tuple is the actual class template of which the type aliased by arithmetic_tuple<int> is a specialization.
The constraints on your alias template don't matter. The result will be the same without them.
Because the mapping of types via the alias is arbitrary, you can't in general write the trait how you seem to intent it. However, with the apriori assumption that the alias template does nothing more than forward the argument list, possibly constrained, to a class template, then you could do:
template<typename... Ts, template<typename...> typename U, template<typename...> typename V>
requires std::same_as<V<Ts...>, U<Ts...>>
struct is_instance_of<V<Ts...>, U> : std::true_type {};
It deduces the class template V independently of the passed template template argument for U and instead checks afterwards if the template argument list of the type would be allowed in U and result in the same type. If the assumption is not satisfied this will result in non-sense though.
Today I was reading the C++17 support page of clang. I've notice something odd. The feature Matching template template parameters to compatible arguments (P0522R0) is marked as partial, because it must be activate through a switch. Their note says:
Despite being the the resolution to a Defect Report, this feature is disabled by default in all language versions, and can be enabled explicitly with the flag -frelaxed-template-template-args in Clang 4. The change to the standard lacks a corresponding change for template partial ordering, resulting in ambiguity errors for reasonable and previously-valid code. This issue is expected to be rectified soon.
What kind of constructs breaks when this feature is activated? Why can it break code and how?
You can have code like this:
template<template<typename> typename>
struct Foo {};
template<typename, typename = void>
struct Bar {};
Foo<Bar> unused;
Without the defect resolution, unused would be ill-formed, because foo takes a template with only one template parameter, and not two. If you relied on this (maybe for SFINAE):
template<template<typename> typename>
void foo();
template<template<typename, typename> typename>
void foo();
template<typename, typename = void>
struct Bar {};
int main() {
foo<Bar>(); // ambiguous after resolution!
}
Then the call would fail! The problem is that there was no corresponding change to partial ordering, and so both candidate functions have the same viability, and the call is ambiguous.
A more common scenario is when some code wants to inspect the template arguments with a set of partial specializations, e.g.:
template<class> struct Foo;
template<template<class> class X, class T>
struct Foo<X<T>> { /* ... */ };
template<template<class, class> class X, class T, class U>
struct Foo<X<T, U>> { /* ... */ };
// etc., etc.
Foo<std::vector<int>> is now ill-formed without an appropriate partial ordering fix.
I encountered a very strange compiler error. For some reason the posted code does compile properly with g++ (7.3.0) while clang (7.0.0) fails:
../TemplateAlias/main.cpp:64:9: error: no matching function for call to 'freeFunc'
freeFunc(new Func, dummyField);
^~~~~~~~
../TemplateAlias/main.cpp:73:12: note: in instantiation of member function 'Helper<Traits<double, ConcreteData, ConcreteField> >::func' requested here
helper.func();
^
../TemplateAlias/main.cpp:21:13: note: candidate template ignored: deduced conflicting templates for parameter '' ('FieldData' vs. 'ConcreteData')
static void freeFunc(SomeFunc<T, FieldData>* func,
^
Both compiler options were set to -std=c++14
template<typename T>
struct ConcreteData
{
T data;
};
template<typename T, template<typename U> class FieldData>
struct ConcreteField
{
FieldData<T> someMember;
};
template<typename T, template<typename U> class FieldData>
struct SomeFunc
{
};
template<typename T, template<typename U> class FieldData>
static void freeFunc(SomeFunc<T, FieldData>* func,
ConcreteField<T, FieldData>& field)
{
// apply the func on data
(void)field; // silence compiler warning
delete func;
}
template<
typename ScalarType,
template<typename U> class FieldDataType,
template<typename U, template <typename X> class Data> class FieldType
>
struct Traits
{
using Scalar = ScalarType;
template<typename T>
using FieldData = FieldDataType<T>;
using Field = FieldType<Scalar, FieldDataType>; // fails with clang only
// using Field = FieldType<Scalar, FieldData>; // using this line helps clang
};
template<typename Traits>
struct Helper
{
// alias all types given by trait for easier access
using Scalar = typename Traits::Scalar;
using Field = typename Traits::Field;
template<typename U>
using DataAlias = typename Traits::template FieldData<U>;
void func()
{
// using Func = SomeFunc<Scalar, DataAlias>; // this line is intended, but fails with both GCC and clang
using Func = SomeFunc<Scalar, Traits::template FieldData>; // compiles only with GCC, fails with clang
Field dummyField;
freeFunc(new Func, dummyField);
}
};
int main()
{
using ConcreteTraits = Traits<double, ConcreteData, ConcreteField>;
Helper<ConcreteTraits> helper;
helper.func();
return 0;
}
According to cppreference.com:
A type alias declaration introduces a name which can be used as a
synonym for the type denoted by type-id. It does not introduce a new
type and it cannot change the meaning of an existing type name. There
is no difference between a type alias declaration and typedef
declaration. This declaration may appear in block scope, class scope,
or namespace scope.
and
Alias templates are never deduced by template argument deduction when
deducing a template template parameter.
In my understanding both types (ConcreteData and FieldData) should be equivalent. Why is clang failing in this condition and why do both compiler fail when using the "second stage" alias? Which compiler is right according to the C++ standard? Is it a compiler bug or a subtle ambiguous interpretation of the C++14 standard?
Borrowing the minimal example of #Oktalist.
template <typename>
class T {};
template <typename _>
using U = T<_>;
template <template <typename> class X>
void f(A<X>, A<X>) {}
if you replace f by:
template <template <typename> class X, template <typename> class Y>
void f(A<X>, A<Y>) {}
the code no longer fail to compile. You can see that the problem is about equivalence of template parameters X and Y, they are deduced to different types.
The equivalence of types produced by alias template are only considered when referring to specialization of the alias, as is specified on [temp.alias]/2:
When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template
Using this rule and the rules for equivalence [temp.type]/1:
T<int> and U<int> are equivalent, so are X<T<int>> and Z<U<int>>, but this rule doesn't extend to the alias template U being equivalent to the class template T (by themselves, they aren't specializations).
This is the same scenario for the alias FieldData and the class template ConcreteData.
There are in fact two defect report, CWG-1286 and CWG-1244 that propose the equivalence extension for alias templates.
I try to understand meaning and implications of 14.5.5/8 of the C++11 standard (idem in C++14 and, I suppose, in C++17)
The type of a template parameter corresponding to a specialized non-type argument shall not be dependent on a parameter of the specialization.
and, as usual, to understand who's correct between g++ and clang++.
The standard show the following example
template <class T, T t> struct C {};
template <class T> struct C<T, 1>; // error
and both g++ and clang++ give error.
So far, so good.
Let's complicate a little the example adding a type
template <typename, typename T, T>
struct foo { };
template <typename T>
struct foo<T, int, 1> { }; // compile
template <typename T>
struct foo<T, T, 1> { }; // error
Both g++ and clang++ compile the first partial specialization (the type of 1, int, isn't a parameter of the specialization) and give error with the second one (the type of 1 is T, a parameter of the specialization)
So far, so good.
Let's introduce a template struct bar with an internal type that doesn't depend from the template parameter
template <typename>
struct bar
{ using type = int; };
and the following program
template <typename>
struct bar { using type = int; };
template <typename, typename T, T>
struct foo { };
template <typename T>
struct foo<T, typename bar<T>::type, 1> { };
int main ()
{ }
it's compiled without error by g++ (tried in wandbox with 4.9.3, 5.5.0, 7.2.0 and head 8.0.0; with c++11, c++14 and, when available, c++17) but clang++ (3.9.1, 4.0.1, 5.0.0, head 6.0.0; c++11, c++14, c++17) give the following error
prog.cc:11:38: error: non-type template argument specializes a template parameter with dependent type 'T'
struct foo<T, typename bar<T>::type, 1> { };
^
prog.cc:7:34: note: template parameter is declared here
template <typename, typename T, T>
~^
As usual: who's right?
clang++, that consider 1 dependent on T (when typename bar<T>::type is fixed as int) or g++ that doesn't relieve this dependencies?
For completeness I have to say that changing bar as follows
template <typename T>
struct bar { using type = T; };
so making the bar<T>::type dependant on T, nothing change: g++ compile without error, and clang++ give the same error.
Look at it from the compiler's point of view.
template <class T, T t> struct C {};
template <class T> struct C<T, 1>; // error
For the specialization, the compiler doesn't know if T can indeed have a value of 1, and so the specialization is invalid.
For
template <typename T>
struct foo<T, typename bar<T>::type, 1> { };
Who's saying that type is always int? You might think that it is obvious, but I could introduce a specialization of bar for one specific T so that type is a std::string:
template<>
struct bar<const volatile int> { using type = std::string };
Basically, your statement "when typename bar<T>::type is fixed as int" is wrong, it is not fixed.
Now what? The standard here says the same thing as for your first example, the specialization is ill-formed, because as your quote correctly states, the type of the non-type parameter depends on another (templated) type of the specialization, namely T, which is unknown. In that regard, clang is right, and gcc is wrong.
I can use std::experimental::is_detected to check whether chips1_t can be instantiated with float* (which it can't) as shown in the static_assert below:
#include <experimental/type_traits>
#include <type_traits>
template <typename, typename = void>
struct chips1;
template <typename T>
struct chips1<T*,std::enable_if_t<std::is_same<T,int>::value>> {
using type = int;
};
template <typename T> using chips1_t = typename chips1<T>::type;
static_assert(!std::experimental::is_detected<chips1_t,float*>::value,"");
If I then try a check using chips2_t, a similar static_assert, shown below, will produce a compilation error; regarding the missing type member. Can anyone tell me why?
struct Base {};
template <typename>
struct chips2;
template <typename T>
struct chips2<T*> : std::enable_if_t<std::is_same<T,int>::value,Base> {
using type = int;
};
template <typename T> using chips2_t = typename chips2<T>::type;
static_assert(!std::experimental::is_detected<chips2_t,float*>::value,"");
In the second case, the base class is not part of the template type, so it's not considered during SFINAE (It's a dependent base class)
Instantiating
template <typename T>
struct chips2<T*>
Succeeds, and then compilation fails because it derives from std::enable_if_t<std::is_same<T,int>::value,Base> which becomes an ill-formed expression.
You cannot specialize templates by their base class*.
For example, you couldn't have two competing specializations like:
template <typename T>
struct chips2<T*> : std::enable_if_t<std::is_same<T,int>::value,Base> {
using type = int;
};
template <typename T>
struct chips2<T*> : std::enable_if_t<!std::is_same<T,int>::value,Base> {
using type = float;
};
They would be considered identical from a specialization standpoint (i.e., they are identical from a naming standpoint, so the compiler will consider the second one to be a redeclaration of the first one). The only part that matters is everything up to the colon :
In the first case, your enable_if is directly part of the template specialization, so SFINAE works properly.
Another note
In the second one, you've effectively made it impossible to instantiate chips2 with anything other than int*, see the following example:
struct Base {};
template <typename>
struct chips2
{};
template <typename T>
struct chips2<T*>
: std::enable_if_t<std::is_same<T,int>::value, Base>
{};
int main(){
chips2<float*> c;
}
You might be inclined to think that SFINAE will choose the base class template for c, but in reality it chooses the specialization chips2<T*> because of what I said above, and compilation fails.
*Relevant standardese at [temp.spec]
the name of the class that is explicitly specialized shall be a simple-template-id.
Where a simple-template-id is of the form:
template-name < template-argument-listopt >
e.g. chips2<T*>. Note that there is no option to also include the class it derives from