函数成员中的enable_if用于无效和继承

时间:2019-01-15 09:01:29

标签: c++ c++11 templates template-meta-programming enable-if

我试图理解为什么此代码无法编译:

// test.h
struct Base
  {
  virtual ~Base{};
  virtual void execute() {}
  virtual void execute(int) {}
  virtual void execute(double) {}
  }

template<class T>
struct Test : Base
  {
    void execute(typename std::enable_if<std::is_void<T>::value, void>::type)
      {
      // Do A
      }

    void execute(typename std::enable_if<!std::is_void<T>::value, int>::type t)
      {
      // Do B
      }
  };

// main.cpp
Test<void> t; 

我收到一个编译器错误:“无类型命名类型”。

即使我使用

修改代码的A版本,也存在相同的错误
std::enable_if<std::is_void<T>::value>

目标是创建一个根据参数T创建不同函数成员的类。在这种情况下2,但我也会对此感兴趣。

[编辑] 我在注释中添加了我正在谈论的继承部分。

4 个答案:

答案 0 :(得分:4)

注意:此答案对于问题的先前编辑很有价值。最近的编辑彻底改变了这个问题,这个答案已经不足够了。

由于execute不是模板函数,因此可能不会涉及SFINAE。实际上,无论何时Test<void>实例化时,execute的两个版本都是相同的,这会导致错误,这不是模板推导失败。

您需要一个功能模板(让我们调用模板参数U),以便从SFINAE中受益。并且由于您需要使用TestT)的相同类型模板参数,因此可以提供默认参数U = T):

解决方案

template<class T>
struct Test
{
    template<class U = T>
    std::enable_if_t<std::is_void_v<U>> execute()
    { std::cout << "is_void\n"; }

    template<class U = T>
    std::enable_if_t<!std::is_void_v<U>> execute()
    { std::cout << "!is_void\n"; }
};

Live demo

答案 1 :(得分:4)

实例化Test<void>时,还实例化了所有成员函数的声明。那只是基本的实例化。那给你什么声明?像这样:

void execute(void);
void execute(<ill-formed> t);

如果您期望SFINAE静默消除格式错误的重载,则需要记住S代表“替代”。将模板参数替换为(成员)功能模板的参数。 execute都不是成员函数模板。它们都是模板专业化的常规成员函数。

您可以通过几种方法对其进行修复。一种方法是制作这两个模板,正确执行SFINAE,并让超载分辨率使您从那里开始。 @YSC已经向您展示了方法。

另一种方法是使用帮助程序模板。这样,您就可以达到最初的目标,即可以随时存在一个成员函数。

template<typename T>
struct TestBase {
  void execute(T t) { }
};

template<>
struct TestBase<void> {
  void execute() { }
};

template<class T>
struct Test : private TestBase<T> {
  using TestBase<T>::execute;
};

您可以选择最适合您的需求。


解决您的修改。我认为第二种方法实际上更适合您的需求。

template<typename T>
struct TestBase : Base {
  void execute(T t) override { }
};

template<>
struct TestBase<void> : Base {
  void execute() override { }
};

TestBase是实现您所追求的目标的中间人。

答案 2 :(得分:2)

还有另一种选择,它不如使用CRTP的选择优雅。它包括在替代程序的主体中进行选择,以使其继续执行基本实现,或者提供该函数的新实现。

如果您使用的是c ++ 17,则多亏if constexpr很简单。在c ++ 11中,替代方法是使用标签分配:

template<class T>
struct Test : Base
  {
    void execute()
      {
      void do_execute(std::integral_constant<bool,std::is_void<T>::value>{});
      }

    void execute(int t)
      {
      void do_execute(std::integral_constant<bool,!std::is_void<T>::value>{}, t);
      }
  private:
  void do_execute(std::integral_constant<bool,true>){
       /*implementation*/
       }
  void do_execute(std::integral_constant<bool,false>){
       Base::execute();//Call directly the base function execute.
                       //Such call does not involve the devirtualization
                       //process.
       }
  void do_execute(std::integral_constant<bool,true>,int t){
       /*implementation*/
       }
  void do_execute(std::integral_constant<bool,false>,int t){
       Base::execute(t);//Call directly the base function execute.
                        //Such call does not involve the devirtualization
                        //process.
       }
  };

使用C ++ 17 if constexpr,它看起来比CRTP解决方案更优雅:

template<class T>
struct Test : Base
  {
    void execute(){
      if constexpr (is_void_v<T>){
         Base::execute();
         }
      else{
        /* implementation */
        }
      }

    void execute(int t){
      if constexpr (!is_void_v<T>){
         Base::execute(t);
         }
      else{
        /* implementation */
        }
      }
  };

答案 3 :(得分:1)

您可以将execute的不同重载封装在一组相关的帮助器类中,如下所示:

template <class T>
struct TestHelper : Base
{
  void execute(int) override {}
};

template <>
struct TestHelper<void> : Base
{
  void execute() override {}
};


template <class T>
struct Test : TestHelper<T>
{
  // Other Test stuff here
};

如果execute的实现实际上依赖于应该在它们之间共享的“其他测试内容”,那么您也可以使用CRTP

template <class T, class Self>
struct TestHelper : Base
{
  void execute(int) override
  {
    Self& self = static_cast<Self&>(*this);
    // Now access all members through `self.` instead of directly
  }
};

template <class Self>
struct TestHelper<void, self> : Base
{
  void execute() override
  {
    Self& self = static_cast<Self&>(*this);
    // Now access all members through `self.` instead of directly
  }
};

template <class T>
struct Test : TestHelper<T, Test>
{
  // Other Test stuff here
};