enable_if 在函数成员中用于 void 和继承

enable_if in function members for void and inheritance

我想了解为什么这段代码无法编译:

// 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; 

我收到一个编译器错误:"no type named type"。

即使我用

修改代码的 A 版本也会出现同样的错误
std::enable_if<std::is_void<T>::value>

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

[编辑] 我已经添加了我在评论中谈论的继承部分。

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

因为execute不是模板函数,所以不会涉及SFINAE。事实上,每当 Test<void> 被实例化时,execute 的两个版本都会被实例化,这会导致一个不是模板推导失败的错误。

您需要一个函数模板(让调用模板参数U)才能从 SFINAE 中受益;由于您需要使用与 Test (T) 相同类型的模板参数,因此您可以提供默认参数 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

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

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

如果您希望 SFINAE 默默地删除格式错误的重载,您需要记住 S 代表 "substitution"。将模板参数替换为(成员)函数模板的参数。 execute 都不是成员函数模板。它们都是模板特化的常规成员函数。

您可以通过多种方式修复它。一种方法是制作这两个模板,正确执行 SFINAE,然后让重载解决方案带您从那里开始。 已经告诉你怎么做了。

另一种方法是使用辅助模板。这样您就可以实现最初的目标,即在任何时候都存在一个成员函数。

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 是完成您所追求的目标的中间人。

您可以将 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 的实现实际上依赖于 "Other Test stuff" 而应该在两者之间共享,您也可以使用 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
};

还有另一种选择,它不如使用 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 */
        }
      }
  };