Search code examples
c++templatesmetaprogrammingsfinae

How to force SFINAE to choose the second definition of structure?


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?


Solution

  • 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>>>;
    };