【问题标题】:Priority when choosing overloaded template functions in C++在 C++ 中选择重载模板函数时的优先级
【发布时间】:2010-11-22 21:24:26
【问题描述】:

我有以下问题:

class Base
{
};

class Derived : public Base
{
};

class Different
{
};

class X
{
public:
  template <typename T>
  static const char *func(T *data)
  {
    // Do something generic...
    return "Generic";
  }

  static const char *func(Base *data)
  {
    // Do something specific...
    return "Specific";
  }
};

如果我现在这样做

Derived derived;
Different different;
std::cout << "Derived: " << X::func(&derived) << std::endl;
std::cout << "Different: " << X::func(&different) << std::endl;

我明白了

Derived: Generic
Different: Generic

但我想要的是,对于从 Base 派生的所有类,都会调用特定方法。 所以结果应该是:

Derived: Specific
Different: Generic

有什么方法可以重新设计 X:func(...)s 来实现这个目标?

编辑:

假设 X::func(...) 的调用者不知道作为参数提交的类是否派生自 Base。所以铸造到基地不是一种选择。 事实上,整个事情背后的想法是 X::func(...) 应该“检测”参数是否来自 Base 并调用不同的代码。 出于性能原因,应该在编译时进行“检测”。

【问题讨论】:

    标签: c++ templates methods overloading


    【解决方案1】:

    只是派生到基类的类型转换

    X::func((Base*)&derived)

    它有效....

    【讨论】:

    • 在 C++ 中,最好使用 static_cast 或 dynamic_cast 来代替 C 风格的强制转换。 (参考史蒂文斯)
    【解决方案2】:

    表达式:

    X::func(derived)
    

    意味着编译器将生成一个有效地具有此签名的声明和代码:

    static const char *func(Derived *data);
    

    结果证明这比你的匹配更好:

    static const char *func(Base *data);
    

    模板函数将用于任何对 typename 合法的东西,例如您用作 T 的任何类,由于编译时策略,它将有效地排除使用您的函数的 Base 版本。

    我的建议是在 X 中为您的特定类型使用specialization,即:

    template <typename T>
      static const char *func(T *data)
      {
        // Do something generic...
        return "Generic";
      }
    
    template <>
      static const char *func(Derived *data) // 'Derived' is the specific type
      {
        // Do something specific...
        return "Specific";
      }
    

    希望有效!

    【讨论】:

    • 不,这行不通。刚刚用 gcc 试了一下。有道理,因为您仍然可以更好地匹配通用版本。
    • 哎呀 - 意味着专门作为静态 const char *func(Derived *data)...这两个在编译器的眼中至少应该是相等的。希望它选择一个明确专业的......
    • 在这里工作。确保您的专业化在类定义之外。基本上,将其移动到父命名空间并将“static”替换为“inline”,并在函数名称前添加“X::”以使其编译。
    • @jscharf:您还没有解决“......我想要的是所有从 Base.. 派生的类”问题的一部分。您是否为每个可能的 Base 子类提议专门化?
    • 函数模板专业化是一个非常糟糕的主意。解决的规则非常混乱。阅读:gotw.ca/publications/mill17.htm
    【解决方案3】:

    您必须为此使用SFINAE。在以下代码中,当且仅当您传递无法(隐式)转换为Base * 的内容时,才能实例化第一个函数。第二个函数则相反。

    您可能想阅读enable_if

    #include <iostream>
    #include <boost/utility/enable_if.hpp>
    #include <boost/type_traits.hpp>
    
    class Base {};
    class Derived : public Base {};
    class Different {};
    
    struct X
    {
        template <typename T>
        static typename boost::disable_if<boost::is_convertible<T *, Base *>,
            const char *>::type func(T *data)
        {
            return "Generic";
        }
    
        template <typename T>
        static typename boost::enable_if<boost::is_convertible<T *, Base *>,
            const char *>::type func(T *data)
        {
            return "Specific";
        }
    };
    
    int main()
    {
        Derived derived;
        Different different;
        std::cout << "Derived: " << X::func(&derived) << std::endl;
        std::cout << "Different: " << X::func(&different) << std::endl;
    }
    

    【讨论】:

    • 好吧,enable_if 很容易实现。然而,is_convertible 似乎完全不同。就个人而言,我不想自己实现它。
    • 没错。然而,其中很多似乎是各种不兼容编译器的解决方法。也许该文件中的某个地方隐藏了一个简短的优雅版本。不过,我真的不想搜索它,我敢打赌你也不会。我会选择 Boost :-)
    • 但是,请注意可以有两个概念:指针可转换性和通用可转换性。以下测试指针可转换性:基本上是:template&lt;typename D, typename B&gt; struct is_ptr_convertible { static char(&amp;is(B*))[1]; static char(&amp;is(...))[2]; static bool const value = (sizeof is((D*)0) == 1); };
    【解决方案4】:

    如果你正在使用 boost,你可以通过一些模板元编程来做到这一点:

    #include <boost/type_traits/is_base_of.hpp>
    
    class X
    {
    private:
        template <typename T>
        static const char *generic_func(T *data)
        {
            // Do something generic...
            return "Generic";
        }
    
        template <typename T>
        static const char *base_func(T *data)
        {
            // Do something specific...
            return "Specific";
        }
    
    public:
        template <typename T>
        static const char* func(T* data)
        {
            if (boost::is_base_of<Base, T>::value)
                return base_func(data);
    
            return generic_func(data);
        }
    };
    

    is_base_of 元函数在编译时进行评估,优化器很可能会删除func 函数中if 的死分支。这种方法允许您拥有多个特定案例。

    【讨论】:

      【解决方案5】:

      我找到了一个非常简单的解决方案!

      class Base
      {
      };
      
      class Derived : public Base
      {
      };
      
      class Different
      {
      };
      
      class X
      {
      private:
        template <typename T>
        static const char *intFunc(const void *, T *data)
        {
          // Do something generic...
          return "Generic";
        }
      
        template <typename T>
        static const char *intFunc(const Base *, T *data)
        {
          // Do something specific...
          return "Specific";
        }
      
      public:
        template <typename T>
        static const char *func(T *data)
        {
          return intFunc(data, data);
        }
      };
      

      这很好用,而且非常纤薄! 诀窍是让编译器通过(否则无用的)第一个参数选择正确的方法。

      【讨论】:

      • +1,我应该想到的。这个技巧实际上在 STL 中被大量使用,以根据传递的迭代器的类别在不同版本的算法之间进行选择。
      • 请记住 Boost 版本,但它可以与可转换性以外的其他特征一起使用。
      【解决方案6】:

      我希望设置重叠 enable_if 的优先级,特别是为了回退调用 STL 容器方法,其中我的特征是诸如 is_assignable is_insterable 之类的东西。与许多容器重叠。

      我想优先分配分配,如果存在,则使用插入迭代器。这是我想出的一个通用示例(由#boost irc 频道中的一些方便的人修改为无限级别的优先级)。它作为优先级的隐式转换将重载排在另一个同样有效的选项之下 - 消除歧义。

      #include <iostream>
      #include <string>
      
      template <std::size_t N>
      struct priority : priority<N - 1> {};
      
      template <>
      struct priority<0> {};
      
      using priority_tag = priority<2>;
      
      template <typename T> 
      void somefunc(T x, priority<0>)
      {
          std::cout << "Any" << std::endl;
      }
      
      template <typename T> 
      std::enable_if_t<std::is_pod<T>::value >
      somefunc(T x, priority<2>)
      {
          std::cout << "is_pod" << std::endl;
      }
      
      template <typename T>
      std::enable_if_t<std::is_floating_point<T>::value >
      somefunc(T x, priority<1>)
      {
          std::cout << "is_float" << std::endl;
      }
      
      int main()
      {
          float x = 1;
          somefunc(x, priority_tag{});
          int y = 1;
          somefunc(y, priority_tag{}); 
          std::string z;
          somefunc(z, priority_tag{});
          return 0;
      }
      

      还有人建议在 C++ 14 中我可以只使用 constexpr if 语句来实现相同的目的,如果 Visual Studio 2015 支持它们,这将更加简洁。希望这对其他人有帮助。

      #include <iostream>
      #include <string>
      
      template <typename T>
      void somefunc(T x)
      {
          if constexpr(std::is_floating_point<T>::value) {
            static_assert(std::is_floating_point<T>::value);
            std::cout << "is_float" << std::endl;
          } else if constexpr(std::is_pod<T>::value) {
            static_assert(std::is_pod<T>::value);
            std::cout << "is_pod" << std::endl;
          } else {
            static_assert(!std::is_floating_point<T>::value);
            static_assert(!std::is_pod<T>::value);
            std::cout << "Any" << std::endl;
          }
      }
      
      int main()
      {
          float x = 1;
          somefunc(x);
          int y = 1;
          somefunc(y); 
          std::string z;
          somefunc(z);
          return 0;
      }
      

      // 感谢 k-ballo @#boost!

      【讨论】:

        猜你喜欢
        • 2023-04-09
        • 2012-12-03
        • 1970-01-01
        • 1970-01-01
        • 2013-03-16
        • 2021-06-10
        • 2017-05-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多