在 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<typename D, typename B> struct is_ptr_convertible static char(&is(B*))[1]; static char(&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++ 中选择重载模板函数时的优先级的主要内容,如果未能解决你的问题,请参考以下文章