【问题标题】:enable_if in function members for void and inheritanceenable_if 在函数成员中用于 void 和继承
【发布时间】:2019-01-15 09:01:28
【问题描述】:

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

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

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

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

【问题讨论】:

  • 问题已编辑@DanielLangr
  • 请不要以使现有答案不正确或不完整的方式编辑问题。有关此类的更多详细信息,请打开第二个后续问题,该问题可以链接到第一个问题。 (但保留这个原样,现在两个版本都有答案。)
  • 好的,完全有道理。谢谢你的建议
  • 很难选择最佳答案,谢谢大家,抱歉编辑

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


【解决方案1】:

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

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

【讨论】:

  • 感谢您的反馈。确实很有趣的解决方案。唯一的问题是,如果除了 execute() 之外还有其他函数成员,我必须再次编写它们以进行专门化。然而,也许这种方法适用于我想到的继承问题:基本上是让 Test::execute 覆盖一个 TestBase::execute 虚函数。另一方面,虚拟机制似乎不起作用
  • @svoltron - 我不知道你的整个问题,但我不明白你为什么需要添加虚函数。 TestBase 也可以持有数据成员来完成execute 的任务。如果你把它变成虚拟的,你也会回到原来的问题。您需要找出 TestBase 中的哪个 execute 才能覆盖它!
  • 正如我在@Angew 回复的评论中所写,我将以某种方式注入基类,这是有道理的,相信我或假设有。直接从这种基类继承并使用 Angew/YSC 方法,不会执行虚拟调用。而是基类函数调用。
  • @svoltron - 嗯。我不确定我是否完全从评论中得到你的方法。我认为你应该听从 YSC 的建议。如果您问另一个问题(希望是Stack Overflow 的一个主题),您将能够解释更多。
  • 当然,谢谢。我已经编辑了这个问题,但如果可能的话,我会问另一个问题
【解决方案2】:

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

因为execute 不是模板函数,所以可能没有 SFINAE involevd。确实,每当Test&lt;void&gt; 被实例化时,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 类继承自 TestBase 并且 execute() 是虚拟的,那会不会有问题?
  • @svoltron 是的。虚函数已经存在,当模板被实例化时,sfinae 就会启动。您可以做的是提供调用虚函数的 sfinae 函数模板。但这超出了这个问题的范围。你可以问另一个。
  • 嗯,我会试着考虑一下,如果我不明白,我会再问一个问题,谢谢
  • @svoltron 不客气。注意:您应该通过单击答案分数下方的复选标记来接受答案;)
  • @Oliv True - 但是在这个答案之后,虚拟功能被添加到问题中。
【解决方案3】:

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

【讨论】:

  • 我正在使用 C++11,可能 CRTP 更优雅如你所说。使用 C++17 确实更好
【解决方案4】:

您可以将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
};

【讨论】:

  • 再次感谢您的反馈。这似乎类似于@StoryTeller 之一,但也包括 CRTP 注入部分。所以这似乎是唯一的方法
  • @svoltron 我希望它有所帮助。但是在以后的问题中请尽量避免XY problems,他们回答起来非常令人沮丧。
猜你喜欢
  • 2017-01-05
  • 1970-01-01
  • 2012-04-12
  • 1970-01-01
  • 1970-01-01
  • 2010-09-08
  • 2023-01-15
相关资源
最近更新 更多