Before that I want to tell that I have tried to implement is_assignable
on my own. There is no need to show me another examples - I have already seen some implementation.
I would like to fix my solution thanks to you (if it's possible, of course) that'll work out.
So, here is my code:
#include <iostream>
#include <type_traits>
#include <utility>
template<typename LambdaT>
struct is_valid_construction {
is_valid_construction(LambdaT) {}
typedef typename LambdaT lambda_prototype;
template<typename ValueTypeT, typename ExprTypeT = decltype(std::declval<lambda_prototype>()(std::declval<ValueTypeT>()))>
struct evaluate {
evaluate(ValueTypeT val) {
std::cout << "Right!";
}
typedef typename std::true_type value;
};
template<typename ValueTypeT> //The compiler ignores this definition
struct evaluate<ValueTypeT, decltype(std::declval<lambda_prototype>()(std::declval<int>()))> {
evaluate(ValueTypeT val) {
std::cout << "Nope";
}
typedef typename std::false_type value;
};
template<typename ValueTypeT>
void print_value(ValueTypeT val) {
evaluate evaluation(val);
}
};
struct ForTest {};
int main() {
is_valid_construction is_assignable([](auto x) -> decltype(x = x) { });
is_valid_construction is_less_comparable([](auto x) -> decltype(x < x) {});
is_valid_construction is_more_comparable([](auto x) -> decltype(x > x) {});
is_assignable.print_value(int{});
is_less_comparable.print_value(char{});
is_more_comparable.print_value(ForTest{});
return 0;
}
As you can see I am trying to define template structure within template structure. So, I excepted that if the invocation (with declval
) of this lambda-expression with parameter of this type (rougly, in terms of substitution) is failed, then SFINAE
goes further and should see that the second template definition could be convenient for instantiation. I am asking how could I fix my template structure and its default parameter to push SFINAE use the second definition?
as @Brian said, you should put the requirements at the primary template if the requirements are for all specializations, and put other requirements for each specialization at their own declarations:
template<typename T, typename = std::void_t</* global requirements */>>
struct S;
template<typename T>
struct S<T, std::void_t</* requirements for this specialization */>>;
and if you want one of specialization is prior to others, you can add its negative requirements to other specializations:
template<typename T, typename = std::void_t</* global requirements */>>
struct S;
template<typename T>
struct S<T, std::void_t<std::enable_if_t</* conditions for this specialization */>>>;
template<typename T>
struct S<T, std::void_t<std::enable_if_t<!/* conditions for the former specialization */>, /* requirements for this specialization */>>;
for your example, it should be like this:
template<typename Lambda>
struct is_valid_construction{
template<typename T, typename = void>
struct helper : std::false_type{};
template<typename T>
struct helper<T, std::void_t<decltype(std::declval<Lambda>()(std::declval<T>()))>> : std::true_type{};
template<typename V, typename = void>
struct evaluate;
template<typename V>
struct evaluate<V, std::enable_if_t<helper<V>::value>>;
template<typename V>
struct evaluate<V, std::void_t<std::enable_if_t<!helper<V>::value>, decltype(std::declval<Lambda>()(std::declval<int>()))>>;
};
by the way, you can use std::is_invocable
to simplify this code:
template<typename Lambda>
struct is_valid_construction{
template<typename V, typename = void>
struct evaluate;
template<typename V>
struct evaluate<V, std::enable_if_t<std::is_invocable_v<Lambda, V>>>;
template<typename V>
struct evaluate<V, std::enable_if_t<!std::is_invocable_v<Lambda, V> && std::is_invocable_v<Lambda, int>>>;
};