是否有可能编写一个模板,根据某个成员函数是否定义在类上而改变行为?

下面是我想写的一个简单的例子:

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”部分。


当前回答

这是c++ 17中另一种实现方法(灵感来自boost:hana)。

该解决方案不需要has_something<T> SFINAE类型trait类。

解决方案

////////////////////////////////////////////
// has_member implementation
////////////////////////////////////////////

#include <type_traits>

template<typename T, typename F>
constexpr auto has_member_impl(F&& f) -> decltype(f(std::declval<T>()), true)
{
  return true;
}

template<typename>
constexpr bool has_member_impl(...) { return false; }

#define has_member(T, EXPR) \
 has_member_impl<T>( [](auto&& obj)->decltype(obj.EXPR){} )

Test

////////////////////////////////////////////
// Test
////////////////////////////////////////////

#include <iostream>
#include <string>

struct Example {
    int Foo;
    void Bar() {}
    std::string toString() { return "Hello from Example::toString()!"; }
};

struct Example2 {
    int X;
};

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr(has_member(T, toString()))
        return obj->toString();
    else
        return "toString not defined";
}

int main() {
    static_assert(has_member(Example, Foo), 
                  "Example class must have Foo member");
    static_assert(has_member(Example, Bar()), 
                  "Example class must have Bar() member function");
    static_assert(!has_member(Example, ZFoo), 
                  "Example class must not have ZFoo member.");
    static_assert(!has_member(Example, ZBar()), 
                  "Example class must not have ZBar() member function");

    Example e1;
    Example2 e2;

    std::cout << "e1: " << optionalToString(&e1) << "\n";
    std::cout << "e1: " << optionalToString(&e2) << "\n";
}

其他回答

如果方法恰好定义在基类中,那么这里由litb提供的标准c++解决方案将不能像预期的那样工作。

处理这种情况的解决方案请参考:

俄语: http://www.rsdn.ru/forum/message/2759773.1.aspx

由罗马人翻译的英文。Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

它非常聪明。然而,这种解决方案的一个问题是,如果被测试的类型不能用作基类(例如基本类型),则会给出编译器错误。

在Visual Studio中,我注意到如果使用没有参数的方法,则需要在参数周围插入一对额外的冗余()来在sizeof表达式中推导()。

这个问题很老了,但是在c++ 11中,我们有了一种新的方法来检查函数是否存在(或者任何非类型成员是否存在),再次依赖SFINAE:

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
    -> decltype(os << obj, void())
{
  os << obj;
}

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
    -> decltype(obj.stream(os), void())
{
  obj.stream(os);
}

template<class T>
auto serialize(std::ostream& os, T const& obj)
    -> decltype(serialize_imp(os, obj, 0), void())
{
  serialize_imp(os, obj, 0);
}

现在来解释一下。首先,我使用表达式SFINAE从重载解析中排除序列化(_imp)函数,如果decltype中的第一个表达式无效(即函数不存在)。

void()用于使所有这些函数的返回类型为空。

如果os << obj重载都可用,则使用0参数优先选择os << obj重载(字面量0是int类型,因此第一个重载是更好的匹配)。


现在,您可能需要一个trait来检查函数是否存在。幸运的是,这很容易写出来。不过,请注意,您需要为可能需要的每个不同函数名自己编写trait。

#include <type_traits>

template<class>
struct sfinae_true : std::true_type{};

namespace detail{
  template<class T, class A0>
  static auto test_stream(int)
      -> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
  template<class, class A0>
  static auto test_stream(long) -> std::false_type;
} // detail::

template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};

生活的例子。

And on to explanations. First, sfinae_true is a helper type, and it basically amounts to the same as writing decltype(void(std::declval<T>().stream(a0)), std::true_type{}). The advantage is simply that it's shorter. Next, the struct has_stream : decltype(...) inherits from either std::true_type or std::false_type in the end, depending on whether the decltype check in test_stream fails or not. Last, std::declval gives you a "value" of whatever type you pass, without you needing to know how you can construct it. Note that this is only possible inside an unevaluated context, such as decltype, sizeof and others.


注意,decltype不一定是必需的,因为sizeof(以及所有未求值的上下文)得到了增强。只是decltype已经交付了一个类型,因此更简洁。下面是其中一个重载的sizeof版本:

template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
    int(*)[sizeof((os << obj),0)] = 0)
{
  os << obj;
}

由于同样的原因,int和long形参仍然存在。数组指针用于提供可以使用sizeof的上下文。

c++ 11的一个简单解决方案:

template<class T>
auto optionalToString(T* obj)
 -> decltype(  obj->toString()  )
{
    return     obj->toString();
}
auto optionalToString(...) -> string
{
    return "toString not defined";
}

更新,3年后:(这是未经测试的)。为了检验是否存在,我认为这是可行的:

template<class T>
constexpr auto test_has_toString_method(T* obj)
 -> decltype(  obj->toString() , std::true_type{} )
{
    return     obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
    return "toString not defined";
}

奇怪的是,竟然没有人建议我在这个网站上看到的下面这个漂亮的把戏:

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上工作,但不确定它是否是标准的。

我的观点是:在不为每一个都创建冗长的类型特征,或使用实验特性或长代码的情况下,普遍地确定某个东西是否可调用:

template<typename Callable, typename... Args, typename = decltype(declval<Callable>()(declval<Args>()...))>
std::true_type isCallableImpl(Callable, Args...) { return {}; }

std::false_type isCallableImpl(...) { return {}; }

template<typename... Args, typename Callable>
constexpr bool isCallable(Callable callable) {
    return decltype(isCallableImpl(callable, declval<Args>()...)){};
}

用法:

constexpr auto TO_STRING_TEST = [](auto in) -> decltype(in.toString()) { return {}; };
constexpr bool TO_STRING_WORKS = isCallable<T>(TO_STRING_TEST);