Reputation: 22346
Compiling this:
template < class T, class Y, class ...Args >
struct isSame
{
static constexpr bool value = std::conditional<
sizeof...( Args ),
typename std::conditional<
std::is_same< T, Y >::value,
isSame< Y, Args... >, // Error!
std::false_type >::type,
std::is_same< T, Y > >::type::value;
};
int main()
{
qDebug() << isSame< double, int >::value;
return EXIT_SUCCESS;
}
Gives me this compiler error:
error: wrong number of template arguments (1, should be 2 or more)
The issue is that isSame< double, int >
has an empty Args
parameter pack, so isSame< Y, Args... >
effectively becomes isSame< Y >
which does not match the signature.
But my question is: Why is that branch being evaluated at all? sizeof...( Args )
is false
, so the inner std:conditional
should not be evaluated. This isn't a runtime piece of code, the compiler knows that sizeof..( Args )
will never be true
with the given template types.
If you're curious, it's supposed to be a variadic version of std::is_same
, not that it works...
Upvotes: 6
Views: 6210
Reputation: 217145
You have an error because the type has to be correct when used as template parameter.
You may use template specialization to solve your issue, something like:
#include <type_traits>
template <typename ... Ts> struct are_same;
template <> struct are_same<> : std::true_type {};
template <typename T> struct are_same<T> : std::true_type {};
template <typename T1, typename T2, typename... Ts>
struct are_same<T1, T2, Ts...> :
std::conditional<
std::is_same<T1, T2>::value,
are_same<T2, Ts...>,
std::false_type
>::type
{};
static_assert(are_same<char, char, char>::value, "all type should be identical");
static_assert(!are_same<char, char, int>::value, "all type should not be identical");
Upvotes: 5
Reputation: 275385
The problem is that conditional is just a template. Its type is dependent on all 3 parameters, even if ::type
field is only dependent on 2 of them (the bool
and whichever it picks).
To do what you want, pass it types that represent how to apply arguments to both sides.m Then pass the arguments to the result.
template<template<class...>class Target>struct defer{
template<class...Ts>using execute=Target<Ts...>;
};
template<class T>struct sink{
template<class...>using execute=T;
};
template< class Prog, class... Ts > using run=Prog::template execute<Ts...>;
using choice = typename std::conditional< test, defer<Foo>, sink< std::false_type > >::type;
using result = run< choice, int, double >;
gives us a conditional that returns a defer
d Foo
, or a sink
d false_type
. We can then run it with some arguments, giving us either Foo<int, double>
or false_type
.
sink
's execute
ignores the arguments and always returns the passed in type, while defer
applies them to the passed in template
.
The trick here is that Foo<int, double>
is not run if test
is false, because in that case choice
is sink<false_type>
!
template<bool b, class A, class B, class...Ts>
using pick = run< typename std::conditional< b, A, B >::type, Ts... >;
using result=pick<test, defer<Foo>, sink<std::false_type>, int, double >;
does the same as choice
/result
above in one line.
To solve your problem, we defer execution on one of your if branches:
static constexpr bool value =
typename std::conditional< sizeof...( Args ),
pick< std::is_same< T, Y >::value,
defer<isSame>,
sink<std::false_type>,
Y, Args...
>,
std::is_same< T, Y >
>::type::value;
now, there are often ways to avoid this technique, but you should know it exists.
Upvotes: 5
Reputation: 63797
std::conditional
In short it's because the template argument substitution happens outside of std::conditional
, which means that it's not std::conditional
that is causing the error.. it's isSame
.
Imagine std::conditional
being a function call (which it isn't), what we pass as parameters are evaulated before the actual body of the function is evaluated, and what we pass as parameters sure must be a valid construct, even if the function itself doesn't use them.
Further reading is available in the below linked answer:
Add some indirection so that you don't instantiate isSame
if sizeof... (Args) == 0
, you could have a traited used as isSame_if_not_empty<Args..>::type
which would yield isSame<Args...>
if Args
is not empty, and something else if it is indeed empty.
Fix so that isSame
can be used with an empty variadic pack, yielding true, this is the sensible approach. If the description of isSame
is "all types passed are of the same type", an empty pack sure has "all of its types" of the same type.
Upvotes: 5
Reputation: 14174
But my question is: Why is that branch being evaluated at all?
Because there is no evaluation, and thats not a branch at all. Thats a template and its instanced to successfully instance the std::conditional
template.
What I mean here is that the evaluation abstraction is usefull when writting template metaprograms, but you should never forget what the template system is really doing.
If you need to conditionally instantiate a potentially ill-formed template, add a level of indirection. Check this answer for an example of that.
Upvotes: 6