是否有可能编写一个模板,根据某个成员函数是否定义在类上而改变行为?
下面是我想写的一个简单的例子:
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");
我也遇到过类似的问题:
一个模板类,可以从少数基类派生,其中一些基类具有某个成员,而另一些基类没有。
我解决它类似于“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;
}
下面是工作代码的示例。
template<typename T>
using toStringFn = decltype(std::declval<const T>().toString());
template <class T, toStringFn<T>* = nullptr>
std::string optionalToString(const T* obj, int)
{
return obj->toString();
}
template <class T>
std::string optionalToString(const T* obj, long)
{
return "toString not defined";
}
int main()
{
A* a;
B* b;
std::cout << optionalToString(a, 0) << std::endl; // This is A
std::cout << optionalToString(b, 0) << std::endl; // toString not defined
}
toStringFn<T>* = nullptr将启用带有额外int参数的函数,该函数的优先级高于使用0调用时需要很长时间的函数。
你可以对函数使用相同的原则,如果函数被实现,返回true。
template <typename T>
constexpr bool toStringExists(long)
{
return false;
}
template <typename T, toStringFn<T>* = nullptr>
constexpr bool toStringExists(int)
{
return true;
}
int main()
{
A* a;
B* b;
std::cout << toStringExists<A>(0) << std::endl; // true
std::cout << toStringExists<B>(0) << std::endl; // false
}
我在另一个线程中对此写了一个答案(与上面的解决方案不同),也检查继承的成员函数:
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不一定是默认可构造的或满足任何特殊的概念。该技术也适用于模板成员。
我急切地等待有关这方面的意见。
可能不像其他例子那么好,但这是我为c++ 11想出的。这适用于选择重载方法。
template <typename... Args>
struct Pack {};
#define Proxy(T) ((T &)(*(int *)(nullptr)))
template <typename Class, typename ArgPack, typename = nullptr_t>
struct HasFoo
{
enum { value = false };
};
template <typename Class, typename... Args>
struct HasFoo<
Class,
Pack<Args...>,
decltype((void)(Proxy(Class).foo(Proxy(Args)...)), nullptr)>
{
enum { value = true };
};
示例使用
struct Object
{
int foo(int n) { return n; }
#if SOME_CONDITION
int foo(int n, char c) { return n + c; }
#endif
};
template <bool has_foo_int_char>
struct Dispatcher;
template <>
struct Dispatcher<false>
{
template <typename Object>
static int exec(Object &object, int n, char c)
{
return object.foo(n) + c;
}
};
template <>
struct Dispatcher<true>
{
template <typename Object>
static int exec(Object &object, int n, char c)
{
return object.foo(n, c);
}
};
int runExample()
{
using Args = Pack<int, char>;
enum { has_overload = HasFoo<Object, Args>::value };
Object object;
return Dispatcher<has_overload>::exec(object, 100, 'a');
}