是否有可能编写一个模板,根据某个成员函数是否定义在类上而改变行为?
下面是我想写的一个简单的例子:
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”部分。
一个使用SFINAE和模板部分特化的例子,通过编写Has_foo概念检查:
#include <type_traits>
struct A{};
struct B{ int foo(int a, int b);};
struct C{void foo(int a, int b);};
struct D{int foo();};
struct E: public B{};
// available in C++17 onwards as part of <type_traits>
template<typename...>
using void_t = void;
template<typename T, typename = void> struct Has_foo: std::false_type{};
template<typename T>
struct Has_foo<T, void_t<
std::enable_if_t<
std::is_same<
int,
decltype(std::declval<T>().foo((int)0, (int)0))
>::value
>
>>: std::true_type{};
static_assert(not Has_foo<A>::value, "A does not have a foo");
static_assert(Has_foo<B>::value, "B has a foo");
static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. ");
static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. ");
static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");
奇怪的是,竟然没有人建议我在这个网站上看到的下面这个漂亮的把戏:
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您可以检查给定的类是否提供了特定的方法。下面是工作代码:
#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测试了它。我不知道它是否可以移植到运行不同编译器的其他平台。
我也遇到过类似的问题:
一个模板类,可以从少数基类派生,其中一些基类具有某个成员,而另一些基类没有。
我解决它类似于“typeof”(Nicola Bonelli)的答案,但使用decltype,所以它在MSVS上编译和正确运行:
#include <iostream>
#include <string>
struct Generic {};
struct HasMember
{
HasMember() : _a(1) {};
int _a;
};
// SFINAE test
template <typename T>
class S : public T
{
public:
std::string foo (std::string b)
{
return foo2<T>(b,0);
}
protected:
template <typename T> std::string foo2 (std::string b, decltype (T::_a))
{
return b + std::to_string(T::_a);
}
template <typename T> std::string foo2 (std::string b, ...)
{
return b + "No";
}
};
int main(int argc, char *argv[])
{
S<HasMember> d1;
S<Generic> d2;
std::cout << d1.foo("HasMember: ") << std::endl;
std::cout << d2.foo("Generic: ") << std::endl;
return 0;
}
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个字母)