Search code examples
c++c++14c++17variadic-templatessfinae

How to simplify enable_if alias in template template parameter


My goal is to have a struct that takes in an alias to a specialized enable_if_t<> along with a typename variadic parameter pack and then tells me whether the enable_if's conditions were satisfied for all of the types in the pack. I have a bunch of these specialized enable_ifs, but need to write tests for them before we can put them into our open source project. I have about 2000+ lines of code manually testing these specializations, but bet I can get it to 100 or 200 if I can figure out the pattern below. I have a working version (+ godbolt link), but tbh I'm not sure why it's working and that scheme is breaking in a case where the implementation receives a parameter pack

Here is an example of the code I would like to write and it's result. I'm using C++14 and can steal basic implementations of things from C++17 likes conjunction and void_t

#include <type_traits>
#include <string>

// enable_if for arithmetic types
template <typename T>
using require_arithmetic = typename std::enable_if_t<std::is_arithmetic<T>::value>;

const bool true_arithmetic = require_tester<require_arithmetic, double, int, float>::value;
// output: true

// If any of the types fail the enable_if the result is false
const bool false_arithmetic = require_tester<require_arithmetic, double, std::string, float>::value;
// output: false

The below does do what I want, but tbf I'm not really understanding how.

// Base impl
 template <template <class> class Check, typename T1, typename = void>
 struct require_tester_impl : std::false_type {};

 // I'm not totally sure why void_t needs to be here?
 template <template <class> class Check, typename T1> 
 struct require_tester_impl<Check, T1, void_t<Check<T1>>> : std::true_type {};

 // The recursive version (stolen conjuction from C++17)
 template <template <class> class Check, typename T = void, typename... Types>
 struct require_tester {
  static const bool value = conjunction<require_tester_impl<Check, T>,
   require_tester<Check, Types...>>::value;
 };

// For the end
 template <template <class> class Check>
 struct require_tester<Check, void> : std::true_type {} ;

In particular, I'm not sure why the void_t is needed in the impl partial specialization for std::true_type.

What I would like to get to is a require_variadic_tester that takes in a variadic templated alias, something like enable_if<conjunction<check<T...>>::value>, and gives me true or false. Sadly, the below returns false no matter what types come in


// impl
template <template <class...> class Check, typename... Types>
struct require_variadic_impl : std::false_type {};

// Adding void_t here causes the compiler to not understand the partial specialiation
template <template <class...> class Check, typename... Types>
struct require_variadic_impl<Check, Check<Types...>> : std::true_type {};

template <template <class...> class Check, typename... Types>
struct require_variadic_tester : require_variadic_impl<Check, Types...> {};

I would like the following given the input, but can't seem to shake how to hide that conjunction one level lower

// Enable if for checking if all types are arithmetic
template <typename... Types>
using require_all_arithmetic = std::enable_if_t<conjunction<std::is_arithmetic<Types>...>::value>;

require_variadic_tester<require_all_arithmetic, double, double, double>::value;
// is true

require_variadic_tester<require_all_arithmetic, double, std::string, double>::value;
// is false

I think my failure to understand void_t in the first meta function is causing my misunderstanding

Below is the godbolt, any help in understanding this is very appreciated!

https://godbolt.org/z/8XNqpo

Edit:

To give more context in why I want the above with the conjunction inside of the enable_if_t. I'm stuck on C++14 but we are adding a new feature to our open source math library which without more generic types (and requirements on those generic types) we will end up with a ton of code bloat. We currently have stuff like this


template <int R, int C>
inline Eigen::Matrix<double, R, C> add(
    const Eigen::Matrix<double, R, C>& m1, const Eigen::Matrix<double, R, C>& m2) {
  return m1 + m2;
}

I'd like to have more generic templates and do something like this


template <typename Mat1, typename Mat2,
  require_all_eigen<is_arithmetic, Mat1, Mat2>...>
inline auto add(Mat1&& m1, Mat2&& m2) {
  return m1 + m2;
}

I have all of those require_*_<container> aliases setup, but the tests for all of those requires is about 2000+ lines and in the future that will be a funky mess to have to deal with.

We have unary and variadic template enable_if aliases, at this point the above unary case does what I want ala a nice test like

#include <gtest/gtest.h>

TEST(requires, arithmetic_test) {
  EXPECT_FALSE((require_tester<require_arithmetic, std::string>::value));
  EXPECT_TRUE((require_tester<require_arithmetic, double, int, float>::value));
}

The issue I have is with testing the variadic template enable_if aliases, where I want to be able to write something like


// Enable if for checking if all types are arithmetic 
template <typename... Types>
using require_all_arithmetic = std::enable_if_t<conjunction<std::is_arithmetic<Types>...>::value>;
/// For the tests

TEST(requires, arithmetic_all_test) {
  EXPECT_FALSE((require_variadic_tester<require_all_arithmetic, std::string,
     Eigen::Matrix<float, -1, -1>>::value));
  EXPECT_TRUE((require_variadic_tester<require_all_arithmetic,
     double, int, float>::value));
}

If I can test all of this I think the requires part of our library alone could be a nice header only mini-library for what I'm calling "bad fake concepts in 14" (or bfc14 for short ;-))


Solution

  • Here's what happens with your require_tester<require_arithmetic, double, double, int>:

    This doesn't match the partial specialization of require_tester, which has just two template arguments <Check, void>, so we use the primary template

    template <template <class> class Check, typename T, typename... Types>
    struct require_tester;
    

    with Check = require_arithmetic; T = double; Types = double, int. It does not match the partial specialization of require_tester. Member value is the result of

    conjunction<require_tester_impl<Check, T>, require_tester<Check, Types...>>::value
    

    where the interesting part is require_tester_impl<Check, T> = require_tester_impl<require_arithmetic, double>. First, since the template parameters of require_tester_impl are

    template <template <class> class Check, typename T1, typename = void>
    

    and only two explicit template argumetns are given, we know the actual template arguments are <require_arithmetic, double, void>. Now we need to see whether or not this matches the partial specialization of require_template_impl, so we try to match:

    require_template_impl<require_arithmetic, double, void>
    require_template_impl<Check, T1, void_t<Check<T1>>>
    

    So template argument deduction finds Check = require_arithmetic and T1 = double. The type void_t<Check<T1>> does not cause any deduction of Check or T1. But the deduced parameter values must be substituted in, and we find void_t<Check<T1>> is void_t<require_arithmetic<double>> is void. This does match the void from the template arguments, so the partial specialization does match, and require_template_impl<require_arithmetic, double, void> inherits std::true_type, not std::false_type.

    On the other hand, if T1 were std::string instead of double, substituting the deduced template arguments in would find void_t<require_arithmetic<std::string>> is invalid, via the eventual enable_if<...>::type where no member type exists. When substituting deduced template arguments into other template parameters fails, this means the partial specialization is thrown out as not a match. So require_template_impl<require_arithmetic, std::string, void> uses the primary template and inherits std::false_type.

    Going back to the value member of require_tester, it recursively finds require_tester<require_arithmetic, double, int>::value via require_tester<require_arithmetic, int>::value via require_tester<require_arithmetic>::value which is the same as require_tester<require_arithmetic, void>::value. All the value members are true, so the final value is true.

    Though I would simplify this a bit:

    1. The void is unnecessary in the require_tester recursion, and causes the strange "fact" that require_tester<Anything, void>::value is always true. It would be better to remove the = void default from the primary require_tester template, and make the base case template <template <class> class Check> require_tester<Check> instead.

    2. Your value expression in the require_tester primary template is always giving exactly two template arguments to conjunction, so it's not really using its variadic property, and you could just as well write require_tester_impl<...>::value && require_tester<...>::value. Since require_tester is doing a recursion itself, it doesn't need the recursive definition abstracted into conjunction. Instead, require_tester could be simplified to count on conjunction and avoid doing any recursion itself:

      template <template <class> class Check, typename... Types>
      struct require_tester : conjunction<require_tester_impl<Check, Types>...>
      {};
      // No other specialization needed.
      

    The require_variadic_tester template can follow a similar pattern, except that I'll give the dummy template parameter which was just typename = void a name, typename Enable. And it needs to come before the template parameter pack, so it's not that useful to actually default it to void, and we need to make sure to use the appropriate void template argument in the corresponding position.

    template <template <class...> class Check, typename Enable, typename... Types>
    struct require_variadic_impl : std::false_type {};
    
    template <template <class...> class Check, typename... Types>
    struct require_variadic_impl<Check, void_t<Check<Types...>>, Types...> : std::true_type {};
    
    template <template <class...> class Check, typename... Types>
    struct require_variadic_tester : require_variadic_impl<Check, void, Types...> {};
    

    See the modified program on godbolt, with desired results.