【问题标题】:std::enable_if to conditionally compile a member functionstd::enable_if 有条件地编译成员函数
【发布时间】:2011-10-21 18:58:05
【问题描述】:

我试图通过一个简单的示例来了解如何使用std::enable_if。在我阅读this answer 之后,我认为想出一个简单的例子应该不会太难。我想使用std::enable_if 在两个成员函数之间进行选择,并且只允许使用其中一个。

不幸的是,以下内容无法使用 gcc 4.7 编译,经过数小时的尝试,我问你们我的错误是什么。

#include <utility>
#include <iostream>

template< class T >
class Y {

    public:
        template < typename = typename std::enable_if< true >::type >
        T foo() {
            return 10;
        }
        template < typename = typename std::enable_if< false >::type >
        T foo() {
            return 10;
        }

};


int main() {
    Y< double > y;

    std::cout << y.foo() << std::endl;
}

gcc 报告以下问题:

% LANG=C make CXXFLAGS="-std=c++0x" enable_if
g++ -std=c++0x    enable_if.cpp   -o enable_if
enable_if.cpp:12:65: error: `type' in `struct std::enable_if<false>' does not name a type
enable_if.cpp:13:15: error: `template<class T> template<class> T Y::foo()' cannot be overloaded
enable_if.cpp:9:15: error: with `template<class T> template<class> T Y::foo()'

为什么 g++ 不删除第二个成员函数的错误实例化?根据标准,std::enable_if&lt; bool, T = void &gt;::type 仅在布尔模板参数为 true 时存在。但是为什么 g++ 不认为这是 SFINAE 呢?我认为重载错误信息来自g++没有删除第二个成员函数的问题,认为这应该是一个重载。

【问题讨论】:

  • 我不确定,但我认为它是以下内容: enable_if 基于 SFINAE (替换失败不是错误)。但是,这里没有任何替换,因为没有参数不能用于确定要使用的重载。你应该让“真”和“假”依赖于 T。(我知道你不想在简单的例子中这样做,但现在可能太简单了......)
  • 我也想到了这一点,并尝试使用std::is_same&lt; T, int &gt;::value! std::is_same&lt; T, int &gt;::value 得到相同的结果。

标签: c++ templates g++ c++11


【解决方案1】:

SFINAE 仅在模板参数的参数推导中的替换使构造格式不正确时才有效。没有这样的替代品。

我也想到了这一点,并尝试使用 std::is_same&lt; T, int &gt;::value! std::is_same&lt; T, int &gt;::value 得到相同的结果。

这是因为当类模板被实例化时(当你创建一个 Y&lt;int&gt; 类型的对象时,它会实例化它的所有成员声明(不一定是它们的定义/主体!)。其中还有它的成员模板。请注意,T 是已知的,!std::is_same&lt; T, int &gt;::value 产生错误。因此它将创建一个类Y&lt;int&gt;,其中包含

class Y<int> {
    public:
        /* instantiated from
        template < typename = typename std::enable_if< 
          std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< true >::type >
        int foo();

        /* instantiated from

        template < typename = typename std::enable_if< 
          ! std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< false >::type >
        int foo();
};

std::enable_if&lt;false&gt;::type 访问一个不存在的类型,因此该声明格式不正确。因此您的程序无效。

您需要使成员模板的enable_if 依赖于成员模板本身的一个参数。那么声明是有效的,因为整个类型仍然是依赖的。当您尝试调用其中一个时,会发生对其模板参数的参数推导,并且 SFINAE 会按预期发生。请参阅this question 以及有关如何执行此操作的相应答案。

【讨论】:

  • ... 澄清一下,以防万一有用:当 Y 模板类的实例被实例化时,编译器实际上不会编译模板成员函数;但是,编译器会将T 替换到成员模板 DECLARATIONS 中,以便以后可以实例化这些成员模板。这个故障点不是 SFINAE,因为 SFINAE 仅适用于确定重载解析的可能函数集,并且实例化类不是确定重载解析的一组函数的情况。 (或者我认为!)
【解决方案2】:

解决这个问题的一种方法,成员函数的特化是将特化放入另一个类,然后从该类继承。您可能必须更改继承顺序才能访问所有其他基础数据,但这种技术确实有效。

template< class T, bool condition> struct FooImpl;
template<class T> struct FooImpl<T, true> {
T foo() { return 10; }
};

template<class T> struct FoolImpl<T,false> {
T foo() { return 5; }
};

template< class T >
class Y : public FooImpl<T, boost::is_integer<T> > // whatever your test is goes here.
{
public:
    typedef FooImpl<T, boost::is_integer<T> > inherited;

    // you will need to use "inherited::" if you want to name any of the 
    // members of those inherited classes.
};

这种技术的缺点是,如果您需要为不同的成员函数测试很多不同的东西,您必须为每个成员创建一个类,并将其链接到继承树中。这适用于访问公共数据成员。

例如:

template<class T, bool condition> class Goo;
// repeat pattern above.

template<class T, bool condition>
class Foo<T, true> : public Goo<T, boost::test<T> > {
public:
    typedef Goo<T, boost::test<T> > inherited:
    // etc. etc.
};

【讨论】:

    【解决方案3】:

    我做了这个简短的例子,它也有效。

    #include <iostream>
    #include <type_traits>
    
    class foo;
    class bar;
    
    template<class T>
    struct is_bar
    {
        template<class Q = T>
        typename std::enable_if<std::is_same<Q, bar>::value, bool>::type check()
        {
            return true;
        }
    
        template<class Q = T>
        typename std::enable_if<!std::is_same<Q, bar>::value, bool>::type check()
        {
            return false;
        }
    };
    
    int main()
    {
        is_bar<foo> foo_is_bar;
        is_bar<bar> bar_is_bar;
        if (!foo_is_bar.check() && bar_is_bar.check())
            std::cout << "It works!" << std::endl;
    
        return 0;
    }
    

    如果您希望我详细说明,请发表评论。我认为代码或多或少是不言自明的,但我又做了一次,所以我可能错了:)

    你可以在here看到它。

    【讨论】:

    • 这不能在 VS2012 上编译。 error C4519: default template arguments are only allowed on a class template.
    • 这很不幸。我只用 gcc 对其进行了测试。也许这有帮助:stackoverflow.com/a/17543296/660982
    • 为什么还要创建另一个模板类Q,即使它等于T
    • 因为您需要模板化test 成员函数。两者不能同时存在。 Q 只是转发类模板类型T。您可以像这样删除类模板Tcpp.sh/4nxw,但这有点违背了目的。
    • 如果你被 C++ Q* 参数添加到您的函数特化中。然后创建一个调用这些适配函数的新函数,向它们传递Q* 类型的附加参数,例如(Q*)NULL。在此处查看:cpp.sh/3d6uj(不要忘记检查 C++98 编译器选项)。
    【解决方案4】:

    布尔值需要依赖于被推导的模板参数。所以一个简单的修复方法是使用默认的布尔参数:

    template< class T >
    class Y {
    
        public:
            template < bool EnableBool = true, typename = typename std::enable_if<( std::is_same<T, double>::value && EnableBool )>::type >
            T foo() {
                return 10;
            }
    
    };
    

    但是,如果您想重载成员函数,这将不起作用。相反,最好使用来自Tick 库的TICK_MEMBER_REQUIRES

    template< class T >
    class Y {
    
        public:
            TICK_MEMBER_REQUIRES(std::is_same<T, double>::value)
            T foo() {
                return 10;
            }
    
            TICK_MEMBER_REQUIRES(!std::is_same<T, double>::value)
            T foo() {
                return 10;
            }
    
    };
    

    您也可以像这样实现自己的成员需要宏(以防万一您不想使用其他库):

    template<long N>
    struct requires_enum
    {
        enum class type
        {
            none,
            all       
        };
    };
    
    
    #define MEMBER_REQUIRES(...) \
    typename requires_enum<__LINE__>::type PrivateRequiresEnum ## __LINE__ = requires_enum<__LINE__>::type::none, \
    class=typename std::enable_if<((PrivateRequiresEnum ## __LINE__ == requires_enum<__LINE__>::type::none) && (__VA_ARGS__))>::type
    

    【讨论】:

    • 那样对我不起作用。也许缺少什么?请您以工作形式重写 OP 示例吗?
    • 原始示例不适用于重载。我更新了我的答案,如何通过重载来做到这一点。
    【解决方案5】:

    来自this 发帖:

    默认模板参数不是模板签名的一部分

    但是可以这样做:

    #include <iostream>
    
    struct Foo {
        template < class T,
                   class std::enable_if < !std::is_integral<T>::value, int >::type = 0 >
        void f(const T& value)
        {
            std::cout << "Not int" << std::endl;
        }
    
        template<class T,
                 class std::enable_if<std::is_integral<T>::value, int>::type = 0>
        void f(const T& value)
        {
            std::cout << "Int" << std::endl;
        }
    };
    
    int main()
    {
        Foo foo;
        foo.f(1);
        foo.f(1.1);
    
        // Output:
        // Int
        // Not int
    }
    

    【讨论】:

    • 它可以工作,但这基本上是模板函数,而不是类本身......它也不允许删除两个相同原型的函数之一(当您需要传递重载时)。不过,这个想法很好。请您以工作形式重写 OP 示例吗?
    • 这对我来说失败了;无论如何,在某些地方(根据链接的答案)使用 typename 而不是 class 有效。 (除非你使用一些不寻常的编译器版本?)
    【解决方案6】:

    对于那些正在寻找“行之有效”的解决方案的后来者:

    #include <utility>
    #include <iostream>
    
    template< typename T >
    class Y {
    
        template< bool cond, typename U >
        using resolvedType  = typename std::enable_if< cond, U >::type; 
    
        public:
            template< typename U = T > 
            resolvedType< true, U > foo() {
                return 11;
            }
            template< typename U = T >
            resolvedType< false, U > foo() {
                return 12;
            }
    
    };
    
    
    int main() {
        Y< double > y;
    
        std::cout << y.foo() << std::endl;
    }
    

    编译:

    g++ -std=gnu++14 test.cpp 
    

    跑步给出:

    ./a.out 
    11
    

    【讨论】:

    • 呃,你为什么要把std::enable_if_t重命名为resolvedType
    • 因为不是每个人都可以使用 C++17,原因可能千差万别。
    • 这个答案是否与the answer above 相同。 (有关可能违反标准的情况,另请参阅下面的评论)
    【解决方案7】:

    这是我使用宏的极简示例。 使用更复杂的表达式时使用双括号enable_if((...))

    template<bool b, std::enable_if_t<b, int> = 0>
    using helper_enable_if = int;
    
    #define enable_if(value) typename = helper_enable_if<value>
    
    struct Test
    {
         template<enable_if(false)>
         void run();
    }
    

    【讨论】:

    • 我明白你为什么想要那个,但这不是惯用的。我们应该鼓励人们避免使用宏,只写template &lt;typename = std::enable_if_t&lt;b, int&gt; = 0&gt;
    【解决方案8】:
    // Try this one:
    
    #include <iostream>
    #include <type_traits>
    
    // suppose you want to disable certain member functions based on the tag
    struct FooTag;
    struct BarTag;
    
    // macro to save some typings in the following
    // note that a dummy typename is involved in both the 
    // first and second parameters. 
    // this should be different than the template parameter of the class (typename T for Widget below)
    
    #define EnableIfFoo(T) \
    template <typename Dummy = void, typename = \
              typename std::enable_if<std::is_same<FooTag, T>::value, Dummy>::type>
    
    #define EnableIfBar(T) \
    template <typename Dummy = void, typename = \
              typename std::enable_if<std::is_same<BarTag, T>::value, Dummy>::type>
    
    template <typename T>
    class Widget {
    public:
        // enable this function only if the tag is Bar
        EnableIfFoo(T)
        void print() const { std::cout << "I am a Foo!" << std::endl; }
        
        // enable this function only if the tag is Foo
        EnableIfBar(T)
        void display() const { std::cout << "I am a Bar!" << std::endl; }
    };
    
    
    int main() {
        
        // instantiate a widget with tag Foo
        // only print is enabled; display is not
        Widget<FooTag> fw;
        fw.print();
        //fw.display(); // compile error !!
        
        // instantiate a Widget using tag Bar
        // only display is enabled; print is not
        Widget<BarTag> bw;
        bw.display();
        //bw.print(); // compile error !!
        
        return 0;
    }
    

    【讨论】:

    • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-17
    • 1970-01-01
    相关资源
    最近更新 更多