在 C++ 中选择重载模板函数时的优先级

Posted

技术标签:

【中文标题】在 C++ 中选择重载模板函数时的优先级【英文标题】:Priority when choosing overloaded template functions in 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 并调用不同的代码。 出于性能原因,应该在编译时进行“检测”。

【问题讨论】:

【参考方案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!

【讨论】:

以上是关于在 C++ 中选择重载模板函数时的优先级的主要内容,如果未能解决你的问题,请参考以下文章

C++中函数模板和模板函数的区别

C++ 提高教程 模板-普通函数与函数模板调用规则

函数重载与函数模板 - C++

函数模板遇上函数重载

C++:一篇文章,带你玩转 函数模板以及重载解析,函数还可以这样玩?

函数重载和模板推导优先级