是否有可能编写一个模板,根据某个成员函数是否定义在类上而改变行为?
下面是我想写的一个简单的例子:
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”部分。
pre -c++20,简单用例的简单选项:
如果你知道你的类是默认可构造的,我们可以使语法更简单。
我们将从最简单的情况开始:默认可构造对象,并且我们知道预期的返回类型。实例方法:
int foo ();
我们可以写出没有declval的类型trait:
template <auto v>
struct tag_v
{
constexpr static auto value = v;
};
template <class, class = int>
struct has_foo_method : tag_v<false> {};
template <class T>
struct has_foo_method <T, decltype(T().foo())>
: tag_v<true> {};
demo
注意,我们将默认类型设置为int,因为这是foo的返回类型。
如果有多个可接受的返回类型,则向decltype添加第二个参数,该参数与默认类型相同,覆盖第一个参数:
decltype(T().foo(), int())
demo
(这里的int类型不重要-我使用它是因为它只有3个字母)
pre -c++20,简单用例的简单选项:
如果你知道你的类是默认可构造的,我们可以使语法更简单。
我们将从最简单的情况开始:默认可构造对象,并且我们知道预期的返回类型。实例方法:
int foo ();
我们可以写出没有declval的类型trait:
template <auto v>
struct tag_v
{
constexpr static auto value = v;
};
template <class, class = int>
struct has_foo_method : tag_v<false> {};
template <class T>
struct has_foo_method <T, decltype(T().foo())>
: tag_v<true> {};
demo
注意,我们将默认类型设置为int,因为这是foo的返回类型。
如果有多个可接受的返回类型,则向decltype添加第二个参数,该参数与默认类型相同,覆盖第一个参数:
decltype(T().foo(), int())
demo
(这里的int类型不重要-我使用它是因为它只有3个字母)
这是个不错的小难题——好问题!
这里有一个替代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;
}
奇怪的是,竟然没有人建议我在这个网站上看到的下面这个漂亮的把戏:
template <class T>
struct has_foo
{
struct S { void foo(...); };
struct derived : S, T {};
template <typename V, V> struct W {};
template <typename X>
char (&test(W<void (X::*)(), &X::foo> *))[1];
template <typename>
char (&test(...))[2];
static const bool value = sizeof(test<derived>(0)) == 1;
};
你必须确保T是一个类。查找foo时的模糊性似乎是替换失败。我让它在gcc上工作,但不确定它是否是标准的。
我在另一个线程中对此写了一个答案(与上面的解决方案不同),也检查继承的成员函数:
SFINAE检查继承的成员函数
以下是该解决方案的一些例子:
例二:
我们正在检查一个具有以下签名的成员:
T::const_iterator begin(
template<class T> struct has_const_begin
{
typedef char (&Yes)[1];
typedef char (&No)[2];
template<class U>
static Yes test(U const * data,
typename std::enable_if<std::is_same<
typename U::const_iterator,
decltype(data->begin())
>::value>::type * = 0);
static No test(...);
static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};
请注意,它甚至检查方法的常量,并且也适用于基本类型。(我的意思是has_const_begin<int>::value为false,不会导致编译时错误。)
示例2
现在我们正在寻找签名:void foo(MyClass&, unsigned)
template<class T> struct has_foo
{
typedef char (&Yes)[1];
typedef char (&No)[2];
template<class U>
static Yes test(U * data, MyClass* arg1 = 0,
typename std::enable_if<std::is_void<
decltype(data->foo(*arg1, 1u))
>::value>::type * = 0);
static No test(...);
static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};
请注意,MyClass不一定是默认可构造的或满足任何特殊的概念。该技术也适用于模板成员。
我急切地等待有关这方面的意见。