如何迫使SFINAE选择结构的第二个定义?

问题描述

在此之前,我想告诉我我曾经尝试自己实现is_assignable。无需再显示其他示例-我已经看到了一些实现。

感谢您的帮助(当然,如果有可能的话),我将解决问题。

所以,这是我的代码

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

如您所见,我正在尝试在模板结构中定义模板结构。因此,我除外,如果使用此类型的参数(很遗憾,就替换而言)的此lambda表达式的调用(带有declval 失败了,然后SFINAE走得更远,应该看到第二个模板定义可以方便地实例化。我问我该如何修复模板结构及其认参数以使用第二个定义来推送SFINAE?

解决方法

SFINAE可以用于指示编译器选择特定的函数重载或类模板的特定部分特殊化。在第一种情况下,替换失败会从重载集中删除声明,而在第二种情况下,替换失败会从考虑中删除部分专业化声明(导致使用主要模板,或者成功替代的其他部分专业化)。 / p>

但是,您在这里尝试做的是向后的:您遇到这样的情况,主模板可能会出现替换错误,并且您提供了部分专业化作为替代。这永远行不通。在模板参数列表到主模板的完全已知之后,将开始进行部分专业化匹配,因此,如果在主模板的模板参数列表中发生替换错误,则无法考虑专业化。

例如,如果我们有

template <typename T,typename U = some_metafunction_of_T>
struct S;

template <typename T>
struct S<T,T>;

然后S<int>的实例化过程将首先对主模板评估U,然后,只有知道TU时,编译器才能确定它们是否相同(这将允许使用部分专业化)。如果在计算U时发生替换错误,甚至不会提出是否应用部分专业化的问题。

要修复代码,必须切换evaluate的两个定义。主要模板必须是“后备”,而部分专业化则可能会遭受替换错误。

,

如@Brian所说,如果要求是针对所有专业的,则应将要求放在主模板上,并将每个专业的其他要求放在自己的声明中:

template<typename T,typename = std::void_t</* global requirements */>>
struct S;
template<typename T>
struct S<T,std::void_t</* requirements for this specialization */>>;

如果您希望某一项专业先于其他专业,则可以将其否定要求添加到其他专业中:

template<typename 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 */>>;

对于您的示例,应该是这样的:

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>()))>>;
};

顺便说一句,您可以使用std::is_invocable来简化此代码:

template<typename Lambda>
struct is_valid_construction{
    template<typename 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>>>;
};
,

由于@RedFog和@Brian,我可以完成我的代码,并且得到了这样的结果:

#include <iostream>
#include <type_traits>
#include <utility>

template<typename LambdaT>
struct is_valid_construction {
    is_valid_construction(LambdaT) {}

    typedef LambdaT lambda_prototype;

    template<class ValueT,class = void>
    struct is_void_t_deducable : std::false_type {};

    template<class ValueT>
    struct is_void_t_deducable<ValueT,std::void_t<decltype(std::declval<lambda_prototype>()(std::declval<ValueT>()))>> : std::true_type {};

    template<class ValueT>
    bool is_valid_for(ValueT value) {
        if constexpr (is_void_t_deducable<ValueT>::value)
            return true;
        else
            return false;
    }
};

struct ForTest {};

int main() {
    is_valid_construction is_assignable([](auto x) -> decltype(x * x) { });
    std::cout << is_assignable.is_valid_for(0) << std::endl;
    std::cout << is_assignable.is_valid_for(ForTest{});

    return 0;
}

正如他们俩所说,当我这样声明模板参数时:

template<typename ValueTypeT,typename ExprTypeT = decltype(std::declval<lambda_prototype>()(std::declval<ValueTypeT>()))>

由于两个声明都不兼容,编译器无法理解第二个模板参数应分配的默认值。

我是模板编程的新手,我可以尝试尽可能简单地解释该解决方案:

第二个模板参数应该是void(如果不是严格地说!)。因此,编译器可以通过第一个声明或第二个声明以两种方式使用第二个void参数实例化模板。

(应该说,如果std::void_t<TemplateParam>正常,void就变成TemplateParam!)

  • 如果使用第二个声明的实例正确,则 第二个模板参数是void

  • 如果第一个声明的实例正确,则 第二个模板参数是void

因此,我们应该帮助编译器使用第二个模板参数void推论两种结构。当它首先尝试实例化is_valid_for(ForTest{})时,它试图推断出 std::void_t<decltype(std::declval<lambda_prototype>()(std::declval<ValueT>()))> 但出现替换错误。但是,没有什么可以阻止以另一种方式推论第二个模板参数void,并且编译器采用第一个声明。

P.S。我知道这个解释不好,但可能会帮助像我这样的假人!