是否有可能编写一个模板,根据某个成员函数是否定义在类上而改变行为?
下面是我想写的一个简单的例子:
template<class T>
std::string optionalToString(T* obj)
{
if (FUNCTION_EXISTS(T->toString))
return obj->toString();
else
return "toString not defined";
}
因此,如果类T定义了toString(),那么它就使用它;否则,它就不会。我不知道如何做的神奇部分是“FUNCTION_EXISTS”部分。
这是个不错的小难题——好问题!
这里有一个替代Nicola Bonelli的解决方案,它不依赖于非标准typeof运算符。
不幸的是,它不能在GCC (MinGW) 3.4.5或Digital Mars 8.42n上工作,但它可以在所有版本的MSVC(包括VC6)和Comeau c++上工作。
较长的注释块有关于它如何工作(或应该如何工作)的详细信息。正如它所说,我不确定哪些行为符合标准-我欢迎对此发表评论。
更新- 2008年11月7日:
看起来,虽然这段代码在语法上是正确的,但MSVC和Comeau c++所显示的行为并不符合标准(感谢Leon Timmermans和litb为我指明了正确的方向)。c++ 03标准说:
14.6.2依赖名称[temp.dep]
段3
在类模板定义中
或类模板的成员,如果
类模板的基类
类型取决于模板参数
基类范围不检查
在非限定名称查找期间
在定义的时候
类的模板或成员
类模板的实例化或
成员。
因此,当MSVC或Comeau考虑T的toString()成员函数在模板实例化时在doToString()中的调用站点执行名称查找时,这看起来是不正确的(尽管它实际上是我在本例中寻找的行为)。
GCC和Digital Mars的行为看起来是正确的——在这两种情况下,非成员toString()函数都绑定到调用。
老鼠-我以为我可能找到了一个聪明的解决方案,但我发现了几个编译器错误…
#include <iostream>
#include <string>
struct Hello
{
std::string toString() {
return "Hello";
}
};
struct Generic {};
// the following namespace keeps the toString() method out of
// most everything - except the other stuff in this
// compilation unit
namespace {
std::string toString()
{
return "toString not defined";
}
template <typename T>
class optionalToStringImpl : public T
{
public:
std::string doToString() {
// in theory, the name lookup for this call to
// toString() should find the toString() in
// the base class T if one exists, but if one
// doesn't exist in the base class, it'll
// find the free toString() function in
// the private namespace.
//
// This theory works for MSVC (all versions
// from VC6 to VC9) and Comeau C++, but
// does not work with MinGW 3.4.5 or
// Digital Mars 8.42n
//
// I'm honestly not sure what the standard says
// is the correct behavior here - it's sort
// of like ADL (Argument Dependent Lookup -
// also known as Koenig Lookup) but without
// arguments (except the implied "this" pointer)
return toString();
}
};
}
template <typename T>
std::string optionalToString(T & obj)
{
// ugly, hacky cast...
optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);
return temp->doToString();
}
int
main(int argc, char *argv[])
{
Hello helloObj;
Generic genericObj;
std::cout << optionalToString( helloObj) << std::endl;
std::cout << optionalToString( genericObj) << std::endl;
return 0;
}
是的,使用SFINAE您可以检查给定的类是否提供了特定的方法。下面是工作代码:
#include <iostream>
struct Hello
{
int helloworld() { return 0; }
};
struct Generic {};
// SFINAE test
template <typename T>
class has_helloworld
{
typedef char one;
struct two { char x[2]; };
template <typename C> static one test( decltype(&C::helloworld) ) ;
template <typename C> static two test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};
int main(int argc, char *argv[])
{
std::cout << has_helloworld<Hello>::value << std::endl;
std::cout << has_helloworld<Generic>::value << std::endl;
return 0;
}
我刚刚用Linux和gcc 4.1/4.3测试了它。我不知道它是否可以移植到运行不同编译器的其他平台。