C++模板特化(无默认函数)

41

我有以下代码,它可以编译并且运行良好:

template<typename T>
T GetGlobal(const char *name);

template<>
int GetGlobal<int>(const char *name);

template<>
double GetGlobal<double>(const char *name);

然而我想移除“default”函数。也就是说,我想让所有对GetGlobal<t>的调用都出错,其中't'既不是int也不是double。
例如,GetGlobal<char>()应该在编译时出错。
我尝试过删除默认函数,但是如我所料,我收到了很多错误。那么有没有一种方法可以“禁用”它,并且只允许调用函数的专门版本呢?
谢谢!
5个回答

40

虽然这是一个旧的过时问题,但值得注意的是 C++11 使用删除函数解决了这个问题:

template<typename T>
T GetGlobal(const char *name) = delete;

template<>
int GetGlobal<int>(const char *name);

更新

MacOS llvm 8下这将无法编译。这是由于一个仍然存在的4年老缺陷(参见此bug报告)。

以下解决方法可解决此问题(使用static_assert结构)。

template<typename T>
T GetGlobal(const char *name) {
    static_assert(sizeof(T) == 0, "Only specializations of GetGlobal can be used");
}

template<>
int GetGlobal<int>(const char *name);

更新

Visual Studio 15.9也存在同样的bug。请使用之前的解决方法。


这是最好的,谢谢!这里也可能相关:https://dev59.com/O2jWa4cB1Zd3GeqPvvRG - egpbos
LLVM 5.0和3.9已经修复了这个bug。 - Roy Falk

32
为了在编译时出现错误,请按以下方式实现:

将其实现为:

template<typename T>
T GetGlobal(const char *name) { T::unimplemented_function; }
// `unimplemented_function` identifier should be undefined

如果你使用 Boost,你可以让它更加优美:
template<typename T>
T GetGlobal(const char *name) { BOOST_STATIC_ASSERT(sizeof(T) == 0); }

C++标准保证不存在大小为0的类型,因此您将获得编译时错误。

正如sbi在评论中提到的,最后一句话可以简化为:

template<typename T>
T GetGlobal(const char *name) { char X[!sizeof(T)]; }

我更喜欢第一种解决方案,因为它提供的错误信息比其他方法更清晰(至少在Visual C++中是如此)。


4
为了让第一个案例在“编译时”失败,Koper 需要一个“未声明”的函数,而不是“未定义”的函数。要使此代码编译通过,唯一的方法是将其依赖于 T。可以使用类似 T::some_thing_that_is_definitely_undeclared 的东西来实现。第二个案例可能可以通过 char dummy[!sizeof(T)]; 来模拟。 - sbi
1
õ¢┐þö¿unimplemented_functionµ»öõ¢┐þö¿T::unimplemented_functionÕ£¿Visual C++õ©¡µÅÉõ¥øµø┤µ©àµÖ░þÜäÚöÖÞ»»õ┐íµü»ÒÇé - Kirill V. Lyadvinsky
1
@Kirill:在进行两阶段查找的编译器中,使用unimplemented_function是行不通的。(目前,VC不支持这种方式。)这样的编译器不会编译模板定义,即使它从未被实例化。 - sbi
2
类似这样的编程内容 template<typename T> struct not_defined : mpl::false_ { }; template<typename T> T GetGlobal(char const *name) { BOOST_MPL_ASSERT(( not_defined<T> )); } 会提供一些漂亮的错误提示:http://codepad.org/rq30wdeq。 - Johannes Schaub - litb
在C++11中,您可以使用static_assert代替BOOST_STATIC_ASSERT - sorosh_sabz
显示剩余3条评论

6

如果您不实现它,至少会得到一个链接器错误。如果您想要编译时错误,您可以使用类模板进行操作:

template<typename T>
struct GlobalGetter;

template<>
struct GlobalGetter<int> {
  static int GetGlobal(const char *name);
};

template<>
struct GlobalGetter<double> {
  static double GetGlobal(const char *name);
};

template<typename T>
T GetGlobal(const char *name)
{
  return GlobalGetter<T>::GetGlobal(name);
}

为什么模板函数的前向声明不会产生编译器错误,而模板类的前向声明会呢? - xtofl
如果您拥有比'int'和'double'更多的专业知识,那么写作量就会太多了。 - Kirill V. Lyadvinsky
2
GlobalGetter<T>(name)是什么?构造函数吗?在GlobalGetter<int>GlobalGetter<double>中没有单参数的构造函数。 - Alexey Malistov
1
因为函数模板声明就足以编译对函数的调用(而对于类,你需要定义)。 - sbi
@Kiril:与其编写函数模板特化,不如为仅包含一个 stat 函数的类模板编写特化。是的,需要多输入一些按键,但并不糟糕。然而,我也喜欢你的第二个解决方案(使用 STATIC_ASSRT)。 - sbi

3
我建议不提供实现,只提供方法的声明。
另一个选择是使用编译时断言。Boost有许多这样的工具。
namespace mpl = boost::mpl;
BOOST_MPL_ASSERT((mpl::or_< boost::same_type<T, double>,
                            boost::same_type<T, int> >));

还有它的消息版本副本,这将会很有帮助。

2
以下是使用boost之外的替代技术:
声明一个依赖名称的typedef
这种方法可行是因为只有当“T”被替换时才会发生对DONT的名称查找。这是类似于Kirill所给出的示例的另一种(但合法的)版本。
template <typename T>
T GetGlobal (const char * name) {
    typedef typename T::DONT CALL_THIS_FUNCTION;
}

使用不完整的返回类型

这种技术对于特化模板并不适用,但是对于重载函数可以使用。其思路是允许声明一个返回不完整类型的函数,但不能调用它:

template <typename T>
class DONT_CALL_THIS_FUNCTION GetGlobal (const char * name);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接