I want to create a meta function that returns a certain type if more than 1 argument is passed to it, and another type based on a condition if only a single argument is passed to it. The condition is arbitrary so it will require an enable_if
or something of that kind, but for this example I'll just make it a type comparison. Let's simplify it to the following
int
, return bool
double
, return int
double
To achieve this I have tried to do the following:
#include <type_traits>
template <typename Enable, typename...Args>
struct Get;
// multiple arguments; return double regardless of the condition
template <typename FirstArg, typename... OtherArgs>
struct Get<typename std::enable_if<true>::type, FirstArg, OtherArgs...>
{
using type = double;
};
// single int; return bool
template <typename Arg>
struct Get<typename std::enable_if<std::is_same<Arg, int>::value>::type, Arg>
{
using type = double;
};
// single double; return int
template <typename Arg>
struct Get<typename std::enable_if<std::is_same<Arg, double>::value>::type, Arg>
{
using type = int;
};
int main()
{
static_assert(std::is_same<typename Get<double>::type, int>::value, "");
static_assert(std::is_same<typename Get<int>::type, bool>::value, "");
static_assert(std::is_same<typename Get<bool, int>::type, double>::value, "");
return 0;
}
Output:
prog.cpp: In function 'int main()': prog.cpp:29:51: error: 'type' in 'struct Get<double>' does not name a type static_assert(std::is_same<typename Get<double>::type, int>::value, ""); ^ prog.cpp:29:60: error: template argument 1 is invalid static_assert(std::is_same<typename Get<double>::type, int>::value, "");
I would be grateful for an answer that teaches me why this doesn't work the way I expected, not just how to fix it. I'm struggling to find good resources on template meta-programming and have so far been programming rather haphazardly, which is something I'd very much like to fix!
The template
arguments to a specialization are not the template arguments to the original definition.
The ones that correspond to the original definition are after the template
name.
The arguments in the template<>
part of the specialization are the types introduced to match the specialization.
So original definition:
template <typename Enable, typename...Args>
struct Get;
1 or more type arguments. And, unless a specialization matches, not defined.
First specialization:
template <typename FirstArg, typename... OtherArgs>
struct Get<typename std::enable_if<true>::type, FirstArg, OtherArgs...> {
using type = double;
};
well, std::enable_if<true>::type
is just void
, so this is the same as:
template <typename FirstArg, typename... OtherArgs>
struct Get<void, FirstArg, OtherArgs...> {
using type = double;
};
So this matches if the first type is void
, and there is at least one other type.
Second specialization:
template <typename Arg>
struct Get<typename std::enable_if<std::is_same<Arg, int>::value>::type, Arg> {
using type = double;
};
So this tries to match if there are two types. The second one is pattern matched.
If it isn't int
, the first one SFINAE fails. If it is an int
, the first one ends up being void
. So this matches Get<void, int>
only.
Third specialization:
template <typename Arg>
struct Get<typename std::enable_if<std::is_same<Arg, double>::value>::type, Arg> {
using type = int;
};
similarly, this matches Get<void, double>
.
Specialization is pattern matching. SFINAE enable_if
clauses cannot be pattern matched. So the pattern match runs, then the enable_if
clauses are evaluated. If they fail, the specialization does not match. If they succeed, the enable_if
clause produces a type. After all types are generated (pattern and not), the resulting list of types either matches or does not.
The easy ways to use this machinery include having your public version forward to a details one, passing void
as the first type, and doing your enable_if
work there.
Another way is to bundle your types up into a list of types, like template<class...>struct types{};
, and pass that as one argument, and void
as the second argument, and do SFINAE again on that void
.
Here is an example:
namespace details {
template<class...>struct types{};
template<class Types, class=void>
struct foo;
template<class T0, class... Ts>
struct foo<types<T0, Ts...>,typename std::enable_if<
std::is_same<T0, int>::value && (sizeof...(Ts)>=1)
>> {
using type=double;
};
}
template<class T0, class... Ts>
struct foo:details::foo< details::types<T0, Ts...> >{};
The specialization of details::foo
pattern matches the types
bundle. It does the enable_if
logic with those pattern matched types. The enable_if
passes if and only if the first type is an int
and there are 1 or more additional types (for an arbitrary set of tests).
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.