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_if
s, 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!
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 ;-))
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:
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.
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.