简体   繁体   中英

How do I enable_if a struct depending on a condition and how many arguments?

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

  1. If a single argument is passed and that argument is an int , return bool
  2. If a single argument is passed and that argument is a double , return int
  3. If more than 1 argument is passed, return 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!

Live example here

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM