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

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

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


当前回答

如果方法恰好定义在基类中,那么这里由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表达式中推导()。

其他回答

这就是类型特征存在的意义。不幸的是,它们必须手动定义。在你的情况下,想象一下:

template <typename T>
struct response_trait {
    static bool const has_tostring = false;
};

template <>
struct response_trait<your_type_with_tostring> {
    static bool const has_tostring = true;
}

虽然这个问题是两年前的事了,但我敢补充我的答案。希望它能澄清之前无可争议的优秀解决方案。我采纳了Nicola Bonelli和Johannes Schaub非常有用的答案,并将它们合并到一个解决方案中,恕我之言,这个解决方案更易于阅读,更清晰,不需要扩展类型:

template <class Type>
class TypeHasToString
{
    // This type won't compile if the second template parameter isn't of type T,
    // so I can put a function pointer type in the first parameter and the function
    // itself in the second thus checking that the function has a specific signature.
    template <typename T, T> struct TypeCheck;

    typedef char Yes;
    typedef long No;

    // A helper struct to hold the declaration of the function pointer.
    // Change it if the function signature changes.
    template <typename T> struct ToString
    {
        typedef void (T::*fptr)();
    };

    template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
    template <typename T> static No  HasToString(...);

public:
    static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};

我用gcc 4.1.2检查了它。 这主要归功于尼古拉·博内利和约翰内斯·绍布,如果我的回答对你有帮助,请给他们投票:)

这是一个c++ 11的解决方案,用于解决“如果我做X,它会编译吗?”

template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
  T,
  type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};

Trait has_to_string使得has_to_string<T>::value为true当且仅当T有一个方法. tostring,该方法在此上下文中可以用0参数调用。

接下来,我将使用标签调度:

namespace details {
  template<class T>
  std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
    return obj->toString();
  }
  template<class T>
  std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
    return "toString not defined";
  }
}
template<class T>
std::string optionalToString(T* obj) {
  return details::optionalToString_helper( obj, has_to_string<T>{} );
}

它比复杂的SFINAE表达式更易于维护。

如果你发现自己经常这样做,你可以用宏来写这些特征,但它们相对简单(每个只有几行),所以可能不值得这样做:

#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};

上面所做的是创建一个宏MAKE_CODE_TRAIT。你向它传递你想要的trait的名字,以及一些可以测试类型t的代码。

MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )

创建上述特征类。

作为题外话,上面的技术是MS所谓的“表达式SFINAE”的一部分,他们的2013编译器失败相当严重。

注意,在c++ 1y中,以下语法是可能的:

template<class T>
std::string optionalToString(T* obj) {
  return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
    return obj.toString();
  }) *compiled_else ([&]{ 
    return "toString not defined";
  });
}

这是一个内联编译条件分支,滥用了大量c++特性。这样做可能是不值得的,因为(代码内联的)好处不值得付出代价(几乎没有人理解它是如何工作的),但是上述解决方案的存在可能会引起人们的兴趣。

c++ 20 -需要表达式

c++ 20带来了一些概念和各种工具,比如require表达式,这是一种检查函数是否存在的内置方式。有了它们,你可以重写optionalToString函数如下:

template<class T>
std::string optionalToString(T* obj)
{
    constexpr bool has_toString = requires(const T& t) {
        t.toString();
    };

    if constexpr (has_toString)
        return obj->toString();
    else
        return "toString not defined";
}

pre - c++ 20 -检测工具包

N4502 proposes a detection toolkit for inclusion into the C++17 standard library that eventually made it into the library fundamentals TS v2. It most likely won't ever get into the standard because it has been subsumed by requires expressions since, but it still solves the problem in a somewhat elegant manner. The toolkit introduces some metafunctions, including std::is_detected which can be used to easily write type or function detection metafunctions on the top of it. Here is how you could use it:

template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );

template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;

注意,上面的例子是未经测试的。标准库中还没有检测工具包,但建议包含了一个完整的实现,如果您确实需要它,可以很容易地复制它。它可以很好地使用c++ 17的特性,如果constexpr:

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

C++14 - 助推哈娜

提振。Hana显然建立在这个特定的例子之上,并在其文档中提供了c++ 14的解决方案,所以我将直接引用它:

[...] Hana provides a is_valid function that can be combined with C++14 generic lambdas to obtain a much cleaner implementation of the same thing: auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { }); This leaves us with a function object has_toString which returns whether the given expression is valid on the argument we pass to it. The result is returned as an IntegralConstant, so constexpr-ness is not an issue here because the result of the function is represented as a type anyway. Now, in addition to being less verbose (that's a one liner!), the intent is much clearer. Other benefits are the fact that has_toString can be passed to higher order algorithms and it can also be defined at function scope, so there is no need to pollute the namespace scope with implementation details.

提振。创科实业

执行这种检查的另一个惯用工具包是Boost,尽管它没有那么优雅。TTI,在Boost 1.54 4.0中引入。对于您的示例,您必须使用宏BOOST_TTI_HAS_MEMBER_FUNCTION。下面是你如何使用它:

#include <boost/tti/has_member_function.hpp>

// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)

// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;

然后,您可以使用bool来创建SFINAE检查。

解释

宏BOOST_TTI_HAS_MEMBER_FUNCTION生成元函数has_member_function_toString,该函数将选中的类型作为其第一个模板参数。第二个模板形参对应于成员函数的返回类型,下面的形参对应于函数形参的类型。如果类T有成员函数std::string toString(),则成员值为true。

或者,has_member_function_toString可以接受成员函数指针作为模板形参。因此,可以将has_member_function_toString<T, std::string>::value替换为has_member_function_toString<std::string T::* ()>::value。

一个使用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");