【问题标题】:constructor overloading and SFINAE构造函数重载和 SFINAE
【发布时间】:2018-03-29 04:32:39
【问题描述】:

作为了解std::enable_if 用法的练习,我尝试实现一个包装类(结构)来表示任何给定时间点的特定类型:

#include<type_traits>
#include<typeinfo>
#include<iostream>
using std::enable_if;
using std::is_same;
using std::cout;
using std::endl;

template<typename T>
struct type_wrap{

                 type_wrap(typename enable_if<is_same<int,T>::value,T>::type&& rrT):value(rrT){
                         cout << "The wrapped type is " << typeid(value).name() << endl;
                         cout << "The wrapped value is " << value << endl;
                } 
                 type_wrap(typename enable_if<is_same<float,T>::value,T>::type && rrT):value(rrT){
                         cout << "The wrapped type is " << typeid(value).name() << endl;
                         cout << "The wrapped value is " << value << endl;
                 }

                 T& value;
};

int main(){

        type_wrap<int>(0);
        type_wrap<float>(0.5);
        return(0);
}

以上代码无法编译:

so_main.cpp:16:47: error: no type named 'type' in 'std::__1::enable_if<false, int>'; 'enable_if' cannot be used to disable this declaration
                 type_wrap(typename enable_if<is_same<float,T>::value,T>::type && rrT):value(rrT){
                                              ^~~~~~~~~~~~~~~~~~~~~~~
so_main.cpp:26:9: note: in instantiation of template class 'type_wrap<int>' requested here
        type_wrap<int>(0);
        ^
so_main.cpp:12:47: error: no type named 'type' in 'std::__1::enable_if<false, float>'; 'enable_if' cannot be used to disable this declaration
                 type_wrap(typename enable_if<is_same<int,T>::value,T>::type&& rrT):value(rrT){
                                              ^~~~~~~~~~~~~~~~~~~~~
so_main.cpp:27:9: note: in instantiation of template class 'type_wrap<float>' requested here
        type_wrap<float>(0.5);
        ^
2 errors generated.

如果我要删除其中一个重载的构造函数以及来自main() 的相应实例化,则该代码有效。但这违背了本练习的全部目的。

有人能指出编译错误的原因吗?

【问题讨论】:

    标签: c++ c++14 enable-if


    【解决方案1】:

    SFINAE 适用于模板方法(/构造函数),这是您的模板类,您可以使用以下内容(即使在您的情况下专业化似乎更简单/更好):

    template<typename T>
    struct type_wrap{
        template <typename U,
                  std::enable_if_t<std::is_same<int, U>::value
                                   && is_same<int, T>::value>* = nullptr>
        type_wrap(U arg) : value(arg){
            // Int case
            std::cout << "The wrapped type is " << typeid(value).name() << std::endl;
            std::cout << "The wrapped value is " << value << std::endl;
        }
    
        template <typename U,
                  std::enable_if_t<std::is_same<float, U>::value
                                   && is_same<float, T>::value>* = nullptr>
        type_wrap(U arg) : value(arg){
            // float case
            std::cout << "The wrapped type is " << typeid(value).name() << std::endl;
            std::cout << "The wrapped value is " << value << std::endl;
        }
        T value;
    };
    

    Demo

    【讨论】:

    • @user9196120:错字已修复,已添加演示。
    • 您可能会注意到,type_wrap&lt;float&gt;(0.5) 对我的版本无效,因为您传递的是 double 而不是 float。而专业化创建允许转换/提升的非模板方法。
    • @@Jarod421 你能解释一下吗?关于为什么使用 * 表示 enable_if_t 并将其设置为 nullptr?我很好奇其中的逻辑,但不确定我是否理解。
    • 您可能会查看why-should-i-avoid-stdenable-if-in-function-signatures,它显示了使用std::enable_if 的不同方式。
    【解决方案2】:

    有人能指出编译错误的原因吗?

    因为std::enable_if 将使您的构造函数之一根据每个构造函数非法:

    type_wrap<int>(0);
    type_wrap<float>(0.5);
    

    intdouble 将强制std::is_same 的另一侧拥有falsein which case std::enable_if has no type

    template<bool B, class T = void>
    struct enable_if {}; // int or float will get this on each constructor.
    
    template<class T>
    struct enable_if<true, T> { typedef T type; };
    

    改为使用模板特化,如下所示:

    template<typename T>
    struct type_wrap;
    
    template<>
    struct type_wrap<float>
    {
        type_wrap(float&& rrT) :value(rrT) {
            cout << "The wrapped type is " << typeid(value).name() << endl;
            cout << "The wrapped value is " << value << endl;
        }
    
        float& value;
    };
    
    template<>
    struct type_wrap<int>
    {
        type_wrap(int&& rrT) :value(rrT) {
            cout << "The wrapped type is " << typeid(value).name() << endl;
            cout << "The wrapped value is " << value << endl;
        }
    
        int& value;
    };
    

    如果您的编译器支持 C++17,if constexpr 会使这变得更容易和更直接:

    template<typename T>
    struct type_wrap
    {
        type_wrap(T&& rrT):value(rrT)
        {
            if constexpr (std::is_same<int, T>::value)
            {
                cout << "The wrapped type is " << typeid(value).name() << endl;
                cout << "The wrapped value is " << value << endl;
            }
            else
            {
                cout << "The wrapped type is " << typeid(value).name() << endl;
                cout << "The wrapped value is " << value << endl;
            }
        } 
    
        T& value;
    };
    

    【讨论】:

    • 感谢您的解释,但我不太明白。我的理解是使用 enable_if() 的目的是根据特定的实例化仅启用一个重载。我不清楚您的观察“int 或 double 将反驳 std::is_same 的另一面为假”。即使我在 main() 中只使用一个实例,即 int 或 float,我也会遇到类似的错误。
    • @user9196120 如果Tfloat,则第一个构造函数是非法的。如果Tint,则第二个构造函数是非法的。在这种情况下没有 SFINAE - 您的编译器必须看到两个在两种情况下都有效的构造函数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多