模板检查是否存在类成员函数?


497

是否可以编写一个模板来更改行为,具体取决于是否在类上定义了某个成员函数?

这是我要写的一个简单示例:

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

因此,如果class T已经toString()定义,则使用它;否则,事实并非如此。我不知道该怎么做的神奇部分是“ FUNCTION_EXISTS”部分。


6
当然,不用说下面的模板答案仅适用于编译时信息,即T必须具有toString。如果在T的子类,通过定义的toString,但T还没有,你会被告知没有定义的toString。
爱丽丝·珀赛尔

可能重复如何检查类中是否存在成员名称(变量或函数)(指定或不指定类型)?,因为它涵盖了C ++ 03到C ++ 1y的更广泛问题。
iammilind

Answers:


319

是的,使用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( typeof(&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上进行了测试。我不知道它是否可以移植到运行不同编译器的其他平台。


18
虽然,我对“一个”和“两个”使用了以下内容:typedef char Small; Big {char dummy [2];}类,以确保与平台相关的变量大小没有歧义。
user23167

6
我怀疑它在地球上是否存在具有sizeof(char)== sizeof(long)的平台
Nicola Bonelli,

17
我不确定,但我认为这不是便携式的。typeof是GCC扩展,在其他编译器上不起作用。
Leon Timmermans

56
不需要typeof-char [sizeof(&C :: helloworld)]也可以。为了避免sizeof(long)== sizeof(char),请使用struct {char [2]};。它的大小必须> = 2
MSalters

57
琐碎,但我花了一段时间才能找出:更换typeof通过decltype使用时的C ++ 0x,例如,通过-std = C ++ 0x中。
hrr 2011年

264

这个问题很旧,但是在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);
}

现在介绍一些解释。首先,我使用表达式SFINAEserialize(_imp)从重载解析中排除函数,如果第一个表达式在其中decltype无效(即该函数不存在)。

void()是用来做所有这些功能的返回类型void

如果两个参数均可用,则该0参数用于首选os << obj重载(字面0量是类型int,因此第一个重载是更好的匹配)。


现在,您可能希望特征检查功能是否存在。幸运的是,编写它很容易。但是请注意,您需要自己为可能需要的每个不同的函数名称编写一个特征。

#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)){};

现场示例。

并进行解释。首先,sfinae_true是辅助类型,它基本上等同于Writing decltype(void(std::declval<T>().stream(a0)), std::true_type{})。优点是它更短。
接下来,取决于签入是否失败,struct has_stream : decltype(...)从最后std::true_type还是std::false_type最后继承。 最后,为您提供传递的任何类型的“值”,而无需知道如何构造它。请注意,这是唯一可能的未计算的上下文中,比如,和其他人。decltypetest_stream
std::declvaldecltypesizeof


请注意,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;
}

出于相同的原因,intlong参数仍然存在。数组指针用于提供sizeof可以使用的上下文。


4
decltypeover 的优点sizeof还在于,通过特殊的函数调用规则不会引入临时变量(因此,您不必具有对返回类型的析构函数的访问权限,并且如果返回类型为,则不会导致隐式实例化。类模板实例化)。
Johannes Schaub-litb 2014年

5
Microsoft尚未在其C ++编译器中实现Expression SFINAE。只是想我可能会帮助节省一些人的时间,因为我很困惑为什么这对我不起作用。不错的解决方案,迫不及待想在Visual Studio中使用它!
乔纳森(Jonathan)

3
您的第一个示例链接已损坏
NathanOliver

1
必须说,static_assert(has_stream<X, char>() == true, "fail X");它将编译而不断言,因为char可转换为int,所以如果不希望这种行为并且希望所有参数类型都匹配,我不知道该如何实现?
加百利2015年

4
如果您像我一样对decltype的两个参数感到困惑,那么decltype实际上只需要一个参数。逗号是此处的运算符。见stackoverflow.com/questions/16044514/...
安德烈·

159

C ++允许将SFINAE用于此目的(注意,使用C ++ 11功能,此操作更为简单,因为它在几乎任意表达式上都支持扩展的SFINAE-以下内容适用于常见的C ++ 03编译器):

#define HAS_MEM_FUNC(func, name)                                        \
    template<typename T, typename Sign>                                 \
    struct name {                                                       \
        typedef char yes[1];                                            \
        typedef char no [2];                                            \
        template <typename U, U> struct type_check;                     \
        template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
        template <typename   > static no  &chk(...);                    \
        static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
    }

上面的模板和宏尝试实例化一个模板,为其提供成员函数指针类型和实际的成员函数指针。如果类型不合适,则SFINAE会导致模板被忽略。用法如下:

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> void
doSomething() {
   if(has_to_string<T, std::string(T::*)()>::value) {
      ...
   } else {
      ...
   }
}

但是请注意,您不能只toString在if分支中调用该函数。由于编译器将检查两个分支的有效性,因此在该函数不存在的情况下将失败。一种方法是再次使用SFINAE(enable_if也可以从boost获取):

template<bool C, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> { };

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> 
typename enable_if<has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T has toString ... */
   return t->toString();
}

template<typename T> 
typename enable_if<!has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T doesnt have toString ... */
   return "T::toString() does not exist.";
}

玩得开心。它的优点是它也适用于重载的成员函数,也适用于const成员函数(请记住std::string(T::*)() const,然后将其 用作成员函数指针类型!)。


7
我喜欢如何type_check用于确保签名完全一致。有没有一种方法可以使它与可以调用带有签名方法的任何方法相匹配Sign?(例如,如果Sign= std::string(T::*)(),则允许std::string T::toString(int default = 42, ...)匹配。)
j_random_hacker 2010年

5
我只是想出一些对我来说不是立即显而易见的东西,以防万一它对其他人有帮助:chk不是,也不需要定义!sizeof运算符确定chk输出的大小,而无需调用chk。
SCFrench 2011年

3
@ deek0146:是的,T一定不能是原始类型,因为T的方法指针声明不受SFINAE约束,并且对于任何非类T都会出错。IMO最简单的解决方案是与is_class来自促进。
Jan Hudec 2012年

2
如果我toString是模板函数,该如何工作?
2012年

4
这是Boost(或其他等效功能)吗?
Dan Nissenbaum 2013年

89

C ++ 20- requires表达式

随着C ++ 20的到来,概念和各种工具(例如requires表达式)成为检查函数是否存在的内置方式。使用它们,您可以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";
}

C ++ 20之前的版本-检测工具包

N4502提出了一种检测工具箱,该工具箱可包含在C ++ 17标准库中,最终将其纳入库基础TS v2。它很可能永远不会成为标准,因为requires从那时起它就被表达式包含了,但是它仍然以某种优雅的方式解决了这个问题。该工具包引入了一些元功能,包括std::is_detected可用于在其顶部轻松编写类型或功能检测元功能的功能。这是使用方法:

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功能配合得很好if constexpr

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

C ++ 14-Boost.Hana

Boost.Hana显然基于此特定示例,并在其文档中提供了C ++ 14的解决方案,因此我将直接引用它:

[...] Hana提供了is_valid可以与C ++ 14通用lambda结合使用的功能,以更简洁地实现同一事物:

auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });

这给我们留下了一个函数对象has_toString,该对象返回给定表达式在传递给它的参数上是否有效。结果以形式返回IntegralConstant,因此constexpr-ness在这里不是问题,因为无论如何该函数的结果都表示为类型。现在,除了不那么冗长(这只是一个衬里!)之外,意图也更加清楚了。其他好处是has_toString可以传递给高阶算法,也可以在函数范围内定义它,因此无需使用实现细节来污染名称空间范围。

增强TTI

Boost.TTI是Boost 1.54.0中引入的另一种惯用的工具包来执行这种检查,尽管它不那么优雅。对于您的示例,您将必须使用macro 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,该元函数将已检查的类型作为其第一个模板参数。第二个模板参数与成员函数的返回类型相对应,以下参数与函数的参数类型相对应。成员value包含trueT是否具有成员函数std::string toString()

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


1
比03
ZFY

@ZFY我认为Boost.TTI也可以与C ++ 03一起工作,但这是其中最不优雅的解决方案。
Morwenn

C ++ 20解决方案真的有效吗?我想要它-但是它被g ++和msvc拒绝-仅被clang接受。
Bernd Baumanns

在cppreference上,您可以阅读:如果require-expression在其要求中包含无效的类型或表达式,并且未出现在模板实体的声明中,则程序格式错误。
Bernd Baumanns

@BerndBaumanns真的吗?我将其与GCC中继一起使用:godbolt.org/z/CBwZdE也许您是对的,我只是检查了它是否有效,但没有按照标准措辞检查它是否合法。
Morwenn

56

尽管这个问题已经有两年历史了,但我敢于添加我的答案。希望它将澄清先前无可争议的出色解决方案。我得到了尼古拉·博内利(Nicola Bonelli)和约翰内斯·绍布(Johannes Schaub)的非常有帮助的答案,并将它们合并为一个解决方案,恕我直言,它更具可读性,清晰性,并且不需要typeof扩展:

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检查了它。信用主要归功于Nicola Bonelli和Johannes Schaub,所以如果我的回答对您有帮助,请给他们投票:)


1
只是想知道,这是否可以做下面康拉德·鲁道夫的解决方案不做的事情?
Alastair Irvine

3
@AlastairIrvine,此解决方案将所有逻辑隐藏在其中,康拉德(Konrad)给用户带来了一些负担。虽然简短易懂,但Konrad的解决方案需要为每个具有的类单独设置模板专门化toString。如果您编写了一个通用库,希望可以与那里的任何类一起工作(例如boost之类的东西),那么要求用户定义一些晦涩的模板的其他专业化可能是不可接受的。有时,最好编写一个非常复杂的代码以使公共接口尽可能简单。
FireAphis 2013年

30

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";
}

4
这是简单而优雅的方法,但严格来说不能回答OP的问题:您不使调用者能够检查函数是否存在,而是始终提供它。但是还是不错。
阿德里安W

@AdrianW,好点。我已经更新了答案。我还没有测试过
Aaron McDaid

万一它对其他人有帮助,我将template<typename>在不知所措的情况下完成这项工作:不考虑解决它。
Laboratorio Cobotica

同样,这是无效的C ++ 11。
彼得

29

这就是那里的类型特征。不幸的是,它们必须手动定义。您的情况如下:

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;
}

5
您应该对特征使用enum代替静态常量: “效果。”
Özgür的

5
“枚举值不是左值(也就是说,它们没有地址)。因此,当您通过引用传递它们时,不使用静态内存。这几乎就像您将计算值作为文字传递一样这些考虑因素促使我们使用枚举值” C ++模板:完整指南
Özgür

22
Comptrol:不,因为整数类型的静态常量是一种特殊情况,因此引用的段落在这里不适用!它们的行为完全像枚举一样,是首选方法。仅在不遵循C ++标准的编译器上才需要使用旧的枚举hack。
康拉德·鲁道夫

3
@Roger Pate:不完全是。这里的“在程序中使用”显然是“引用”的同义词。通俗易懂的这段文字以及所有现代C ++编译器都实现的文字,是您可以采用静态常量的而无需声明它(上一句话说:“…成员可以出现在整数常量表达式中。 …”)。你需要,如果你把它的地址来定义它(通过显式&T::x或隐式绑定到一个参考)。
康拉德·鲁道夫


25

嗯,这个问题已经有很长的答案了,但是我想强调一下Morwenn的评论:C ++ 17的提议使它变得更加简单。参见N4502详细信息,,但作为一个独立的示例,请考虑以下内容。

这部分是常量部分,将其放在标题中。

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

然后是可变部分,您可以在其中指定要查找的内容(类型,成员类型,函数,成员函数等)。对于OP:

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

template <typename T>
using has_toString = detect<T, toString_t>;

以下示例取自N4502,它显示了一个更精致的探针:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

与上述其他实现相比,该实现非常简单:减少了一组工具(void_tdetect)就足够了,不需要繁琐的宏。此外,据报道(请参阅N4502)它比以前的方法要有效得多(编译时和编译器内存消耗)。

这是一个生动的例子。它可以在Clang上正常工作,但是不幸的是,5.1之前的GCC版本遵循了对C ++ 11标准的不同解释,这导致void_t其无法按预期工作。Yakk已经提供了解决方法:使用以下定义void_t参数列表中的void_t有效,但不作为返回类型):

#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.com/a/28967049/1353549
template <typename...>
struct voider
{
  using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif

是否可以将其扩展为检测非成员函数?
–plasmcel

是的,当然。仔细看一下示例:您基本上提供了一个表达式并检查它是否有效。不需要此表达式仅涉及成员函数调用。
akim '16

N4502(open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf)是未来的方式...我一直在寻找一种检测类型上事物的简洁方法,而N4502则是一种方法去。
tlonuk

11

如果“如果我使用X,它将编译吗?”,这是C ++ 11的通用问题解决方案。

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 {};

性状has_to_string使得has_to_string<T>::valuetrue当且仅当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。您为它传递所需特征的名称,以及一些可以测试该类型的代码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 ++功能。这样做可能不值得,因为(代码是内联的)好处(不值得几乎没人了解它的工作原理)的代价,但是上述解决方案的存在可能会引起人们的兴趣。


这可以处理私人案件吗?
tower120 2014年

@ tower120我必须进行实验:模板如何与私有/公共/受保护的交互对我来说有点晦涩。但是,在哪里调用都无关紧要has_to_string
Yakk-Adam Nevraumont 2014年

但是,如果从另一侧看……我们可以从派生类中接触受保护的成员。也许如果将所有这些东西放入INSIDE类中,并从结构转换为constexpr函数……
tower120 2014年

在这里,请看此coliru.stacked-crooked.com/a/ee94d16e7c07e093我只是无法将它
设为


10

以下是一些用法摘要:*所有这些的胆量都进一步降低了

检查x给定班级的成员。可以是var,func,class,union或enum:

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;

检查成员功能void x()

//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;

检查成员变量x

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;

检查会员等级x

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

检查会员工会x

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

检查成员枚举x

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

检查任何成员函数,x无论是否签名:

CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

要么

CREATE_MEMBER_CHECKS(x);  //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

详细信息和核心:

/*
    - Multiple inheritance forces ambiguity of member names.
    - SFINAE is used to make aliases to member names.
    - Expression SFINAE is used in just one generic has_member that can accept
      any alias we pass it.
*/

//Variadic to force ambiguity of class members.  C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};

//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};

template<typename A, typename = void>
struct got_type : std::false_type {};

template<typename A>
struct got_type<A> : std::true_type {
    typedef A type;
};

template<typename T, T>
struct sig_check : std::true_type {};

template<typename Alias, typename AmbiguitySeed>
struct has_member {
    template<typename C> static char ((&f(decltype(&C::value))))[1];
    template<typename C> static char ((&f(...)))[2];

    //Make sure the member name is consistently spelled the same.
    static_assert(
        (sizeof(f<AmbiguitySeed>(0)) == 1)
        , "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
    );

    static bool const value = sizeof(f<Alias>(0)) == 2;
};

巨集(El Diablo!):

CREATE_MEMBER_CHECK:

//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member)                                         \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct Alias_##member;                                                      \
                                                                            \
template<typename T>                                                        \
struct Alias_##member <                                                     \
    T, std::integral_constant<bool, got_type<decltype(&T::member)>::value>  \
> { static const decltype(&T::member) value; };                             \
                                                                            \
struct AmbiguitySeed_##member { char member; };                             \
                                                                            \
template<typename T>                                                        \
struct has_member_##member {                                                \
    static const bool value                                                 \
        = has_member<                                                       \
            Alias_##member<ambiguate<T, AmbiguitySeed_##member>>            \
            , Alias_##member<AmbiguitySeed_##member>                        \
        >::value                                                            \
    ;                                                                       \
}

CREATE_MEMBER_VAR_CHECK:

//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name)                                   \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_var_##var_name : std::false_type {};                      \
                                                                            \
template<typename T>                                                        \
struct has_member_var_##var_name<                                           \
    T                                                                       \
    , std::integral_constant<                                               \
        bool                                                                \
        , !std::is_member_function_pointer<decltype(&T::var_name)>::value   \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_SIG_CHECK:

//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix)    \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_func_##templ_postfix : std::false_type {};                \
                                                                            \
template<typename T>                                                        \
struct has_member_func_##templ_postfix<                                     \
    T, std::integral_constant<                                              \
        bool                                                                \
        , sig_check<func_sig, &T::func_name>::value                         \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_CLASS_CHECK:

//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name)               \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_class_##class_name : std::false_type {};  \
                                                            \
template<typename T>                                        \
struct has_member_class_##class_name<                       \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_class<                                    \
            typename got_type<typename T::class_name>::type \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_UNION_CHECK:

//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name)               \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_union_##union_name : std::false_type {};  \
                                                            \
template<typename T>                                        \
struct has_member_union_##union_name<                       \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_union<                                    \
            typename got_type<typename T::union_name>::type \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_ENUM_CHECK:

//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name)                 \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_enum_##enum_name : std::false_type {};    \
                                                            \
template<typename T>                                        \
struct has_member_enum_##enum_name<                         \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_enum<                                     \
            typename got_type<typename T::enum_name>::type  \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_CHECK:

//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func)          \
template<typename T>                            \
struct has_member_func_##func {                 \
    static const bool value                     \
        = has_member_##func<T>::value           \
        && !has_member_var_##func<T>::value     \
        && !has_member_class_##func<T>::value   \
        && !has_member_union_##func<T>::value   \
        && !has_member_enum_##func<T>::value    \
    ;                                           \
}

CREATE_MEMBER_CHECKS:

//Create all the checks for one member.  Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member)    \
CREATE_MEMBER_CHECK(member);            \
CREATE_MEMBER_VAR_CHECK(member);        \
CREATE_MEMBER_CLASS_CHECK(member);      \
CREATE_MEMBER_UNION_CHECK(member);      \
CREATE_MEMBER_ENUM_CHECK(member);       \
CREATE_MEMBER_FUNC_CHECK(member)

1
您是否知道为什么我们要更改sig_check<func_sig, &T::func_name>为自由功能检查:sig_check<func_sig, &func_name>它无法使用“未声明的标识符”进行构建,并提及我们要检查的功能名称?因为我希望SFINAE不会出错,所以它只对成员这样做,为什么不对自由函数呢?
v.oddou

我认为这与自由函数不是类或结构有关。这种推论成员存在的技术实际上集中在C ++中的多重继承机制上,迫使存根类之间存在歧义,而存根类仅用于托管您要检查的成员与您实际上要检查的成员的类in。这是一个有趣的问题,没有考虑过。您可能会检查其他C ++ 11/14成员检查技术,我在新标准中看到了一些聪明的东西。
Brett Rossier 2015年

谢谢您的回答,我想我可能需要更深入地了解intel对继承的影响,因为到目前为止,我还没有看到仅仅依靠SFINAE来做出表达不正确的表达之间的任何关联。模板类型参数中的成员,并且具有多重继承。但是我完全相信,在C ++中,即使是遥远的概念也可能相互渗透。现在,对于自由函数,这个问题很有趣:stackoverflow.com/questions/26744589 TC答案似乎使用了一种声明虚拟
变量

8

我在另一个线程中写了一个答案(与上面的解决方案不同),该线程还检查了继承的成员函数:

SFINAE检查继承的成员函数

这是该解决方案的一些示例:

范例1:

我们正在检查具有以下签名的成员: T::const_iterator begin() const

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是假的,不会引起编译时错误。)

例子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不一定是默认可构造的,也不必满足任何特殊概念。该技术也适用于模板成员。

我热切地等待对此的意见。


7

现在,这是一个不错的选择小难题-问题!

这是不依赖非标准运营商的Nicola Bonelli解决方案的替代方案typeof

不幸的是,它不能在GCC(MinGW)3.4.5或Digital Mars 8.42n上运行,但可以在所有版本的MSVC(包括VC6)和Comeau C ++上运行。

较长的注释块包含有关其工作原理(或应该工作)的详细信息。如它所说,我不确定哪种行为符合标准-我欢迎对此发表评论。


更新-2008年11月7日:

看起来虽然该代码在语法上是正确的,但MSVC和Comeau C ++所显示的行为未遵循标准(感谢Leon Timmermanslitb为我指明了正确的方向)。C ++ 03标准指出:

14.6.2从属名称[temp.dep]

第3段

在类模板或类模板的成员的定义中,如果类模板的基类取决于template-parameter,则在类的定义时,在非限定名称查找期间都不会检查基类作用域模板或成员,或在类模板或成员的实例化期间。

因此,看起来当MSVC或Comeau考虑在实例化模板时在调用站点执行名称查找的toString()成员函数时,这是不正确的(即使实际上是我在这种情况下要寻找的行为)。TdoToString()

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;
}

1
不,它不符合标准,但是我认为如果打开-fpermissive选项,它将在GCC中起作用。
Leon Timmermans

我知道这些评论并没有太多余地,但是您能否指出为什么它不符合标准?(我不争论-我很好奇)
Michael Burr

Mike B:标准在3.10 p15中说:“如果程序尝试通过以下值之一以外的左值访问对象的存储值,则行为是不确定的”,该列表确实不包含您做。
约翰尼斯·绍布

4
我不确定为什么它不添加我的另一条评论:您的toString调用不合格。因此它将始终调用free函数,而永远不会调用基函数,因为baseclass依赖于模板类型参数。
Johannes Schaub-小提琴

@litb:感谢您的指导。我认为3.10在这里不适用。doToString()内部对toString()的调用不是“通过左值访问对象的存储值”。但是您的第二条评论是正确的。我将更新答案。
Michael Burr

6

如果方法恰好在基类中定义,那么litb在此处提供的标准C ++解决方案将无法按预期工作。

有关处理这种情况的解决方案,请参阅:

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

Roman.Perepelitsa的英语翻译:http ://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

这是疯狂的聪明。但是,这种解决方案的一个问题是,如果所测试的类型不能用作基类(例如原始类型),则会产生编译器错误。

在Visual Studio中,我注意到,如果使用不带参数的方法,则需要在参数周围插入一对多余的()冗余,以在sizeof表达式中推断()。


嗯,已经使用发布想法的方式开发了自己的版本,我发现这个想法还有其他缺点,因此我再次从答案中删除了代码。一种是所有功能必须在目标类型中是公共的。因此,您无法在其中检查“ f”函数:struct g { void f(); private: void f(int); };因为其中一个函数是私有的(这是因为代码执行了using g::f;,所以如果f无法访问则失败)。
Johannes Schaub-litb

6

MSVC具有__if_exists和__if_not_exists关键字(Doc)。结合Nicola的typeof-SFINAE方法,我可以像OP一样为GCC和MSVC创建检查。

更新:来源可以在这里找到


6

通过编写Has_foo概念检查使用SFINAE和模板部分专业化的示例:

#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");

5

我修改了https://stackoverflow.com/a/264088/2712152中提供的解决方案,使其更加通用。另外,由于它不使用任何新的C ++ 11功能,因此我们可以将其与旧的编译器一起使用,并且也应与msvc一起使用。但是,由于编译器使用可变参数宏,因此它们应使C99能够使用它。

以下宏可用于检查特定类是否具有特定typedef。

/** 
 * @class      : HAS_TYPEDEF
 * @brief      : This macro will be used to check if a class has a particular
 * typedef or not.
 * @param typedef_name : Name of Typedef
 * @param name  : Name of struct which is going to be run the test for
 * the given particular typedef specified in typedef_name
 */
#define HAS_TYPEDEF(typedef_name, name)                           \
   template <typename T>                                          \
   struct name {                                                  \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U>                                       \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<typename _1::typedef_name>*);    \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

以下宏可用于检查特定类是否具有特定成员函数以及是否具有给定数量的参数。

/** 
 * @class      : HAS_MEM_FUNC
 * @brief      : This macro will be used to check if a class has a particular
 * member function implemented in the public section or not. 
 * @param func : Name of Member Function
 * @param name : Name of struct which is going to be run the test for
 * the given particular member function name specified in func
 * @param return_type: Return type of the member function
 * @param ellipsis(...) : Since this is macro should provide test case for every
 * possible member function we use variadic macros to cover all possibilities
 */
#define HAS_MEM_FUNC(func, name, return_type, ...)                \
   template <typename T>                                          \
   struct name {                                                  \
      typedef return_type (T::*Sign)(__VA_ARGS__);                \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U, U>                                    \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<Sign, &_1::func>*);              \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

我们可以使用上面的两个宏来对has_typedef和has_mem_func进行检查:

class A {
public:
  typedef int check;
  void check_function() {}
};

class B {
public:
  void hello(int a, double b) {}
  void hello() {}
};

HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);

int main() {
  std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
  std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
  std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
  std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
  std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
  std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
  std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
  std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}

您可以对此进行改进,以支持带有模板参数的成员函数。将模板<typename T>更改为模板<typename T,typename ... Args>,然后可以在宏省略号中使用“ Args ...”来创建带有可变模板args的检查结构。例如。检测到“ void onNext(const T&)”方法HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... ); ...template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
ACyclic

4

奇怪的是没有人建议我在这个网站上见过以下有趣的技巧:

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


3

可用于检查类型是否支持某些“功能”的通用模板:

#include <type_traits>

template <template <typename> class TypeChecker, typename Type>
struct is_supported
{
    // these structs are used to recognize which version
    // of the two functions was chosen during overload resolution
    struct supported {};
    struct not_supported {};

    // this overload of chk will be ignored by SFINAE principle
    // if TypeChecker<Type_> is invalid type
    template <typename Type_>
    static supported chk(typename std::decay<TypeChecker<Type_>>::type *);

    // ellipsis has the lowest conversion rank, so this overload will be
    // chosen during overload resolution only if the template overload above is ignored
    template <typename Type_>
    static not_supported chk(...);

    // if the template overload of chk is chosen during
    // overload resolution then the feature is supported
    // if the ellipses overload is chosen the the feature is not supported
    static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value;
};

用于检查是否存在foo与签名兼容的方法的模板double(const char*)

// if T doesn't have foo method with the signature that allows to compile the bellow
// expression then instantiating this template is Substitution Failure (SF)
// which Is Not An Error (INAE) if this happens during overload resolution
template <typename T>
using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>())));

例子

// types that support has_foo
struct struct1 { double foo(const char*); };            // exact signature match
struct struct2 { int    foo(const std::string &str); }; // compatible signature
struct struct3 { float  foo(...); };                    // compatible ellipsis signature
struct struct4 { template <typename T>
                 int    foo(T t); };                    // compatible template signature

// types that do not support has_foo
struct struct5 { void        foo(const char*); }; // returns void
struct struct6 { std::string foo(const char*); }; // std::string can't be converted to double
struct struct7 { double      foo(      int *); }; // const char* can't be converted to int*
struct struct8 { double      bar(const char*); }; // there is no foo method

int main()
{
    std::cout << std::boolalpha;

    std::cout << is_supported<has_foo, int    >::value << std::endl; // false
    std::cout << is_supported<has_foo, double >::value << std::endl; // false

    std::cout << is_supported<has_foo, struct1>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct2>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct3>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct4>::value << std::endl; // true

    std::cout << is_supported<has_foo, struct5>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct6>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct7>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct8>::value << std::endl; // false

    return 0;
}

http://coliru.stacked-crooked.com/a/83c6a631ed42cea4


有没有办法将内联has_foo到的模板调用中is_supported。我想要的是这样的名称:std::cout << is_supported<magic.foo(), struct1>::value << std::endl;。原因是,我想has_foo为每个要检查的功能签名定义一个,然后才能检查该功能?
CJCombrink '17

2

这个解决方案怎么样?

#include <type_traits>

template <typename U, typename = void> struct hasToString : std::false_type { };

template <typename U>
struct hasToString<U,
  typename std::enable_if<bool(sizeof(&U::toString))>::type
> : std::true_type { };

如果toString超载(如&U::toString歧义)失败。
Yakk-Adam Nevraumont 2014年

@Yakk我认为演员可以解决此问题。
user1095108 2014年

2

这里有很多答案,但是我没有找到一个可以执行真实方法解析顺序的版本,却没有使用任何较新的c ++功能(仅使用c ++ 98功能)。
注意:此版本已经过测试,可与vc ++ 2013,g ++ 5.2.0和在线编译器一起使用。

所以我想出了一个只使用sizeof()的版本:

template<typename T> T declval(void);

struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);

struct yes { char v[1]; };
struct no  { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};

template<typename T>
struct has_awesome_member {
 template<typename U> static yes_no<(sizeof((
   declval<U>().awesome_member(),fake_void()
  ))!=0)> check(int);
 template<typename> static no check(...);
 enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};


struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };

static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");

实时演示(具有扩展的返回类型检查和vc ++ 2010解决方法): http //cpp.sh/5b2vs

没有资料,因为我自己想出了。

在g ++编译器上运行Live演示时,请注意,数组大小允许为0,这意味着使用的static_assert即使失败也会不会触发编译器错误。
一种常用的解决方法是用“ extern”替换宏中的“ typedef”。


不,但是我自己声明它,并且它不使用rvalue(请参阅代码顶部)。或者,您也可以说服自己,并在c ++ 98模式下尝试现场演示。PS:static_assert也不是c ++ 98,但是有变通方法(实时演示)
user3296587 2015年

天哪!错过了。:-)
Ian Ni-Lewis

您的静态断言不起作用。您需要使用数组大小​​-1而不是0(尝试放置static_assert(false);)。我将它与CRTP结合使用,我想确定派生类是否具有特定功能-事实证明它不起作用,但您的断言始终会通过。我失去了那根头发。

我假设您正在使用g ++。请注意,GCC /克++具有延伸部,其允许零大小的阵列(gcc.gnu.org/onlinedocs/gcc/Zero-Length.html
user3296587

您是否可以重写此代码以免重载运算符?例如选择另一个运营商?另外,还要避免使用has_awesome_member以外的名称污染名称空间?
einpoklum

1

这是我的版本,它以任意Arity处理所有可能的成员函数重载,包括模板成员函数,可能还有默认参数。在给定arg类型的情况下,当对某个类类型进行成员函数调用时,它可以区分3种互斥的场景:(1)有效,或(2)模棱两可,或(3)不可行。用法示例:

#include <string>
#include <vector>

HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)

struct test
{
   void bar(int);
   void bar(double);
   void bar(int,double);

   template < typename T >
   typename std::enable_if< not std::is_integral<T>::value >::type
   bar(const T&, int=0){}

   template < typename T >
   typename std::enable_if< std::is_integral<T>::value >::type
   bar(const std::vector<T>&, T*){}

   template < typename T >
   int bar(const std::string&, int){}
};

现在您可以像这样使用它:

int main(int argc, const char * argv[])
{
   static_assert( has_mem_bar<test>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
   static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
   static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
   static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
   static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");

   static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
   static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");

   static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
   static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");

   static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");

   return 0;
}

这是用c ++ 11编写的代码,但是,您可以轻松地(稍作调整)将其移植到具有typeof扩展名的非c ++ 11(例如gcc)。您可以使用自己的宏替换HAS_MEM宏。

#pragma once

#if __cplusplus >= 201103

#include <utility>
#include <type_traits>

#define HAS_MEM(mem)                                                                                     \
                                                                                                     \
template < typename T >                                                                               \
struct has_mem_##mem                                                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  struct ambiguate_seed { char mem; };                                                               \
  template < typename U > struct ambiguate : U, ambiguate_seed {};                                   \
                                                                                                     \
  template < typename U, typename = decltype(&U::mem) > static constexpr no  test(int);              \
  template < typename                                 > static constexpr yes test(...);              \
                                                                                                     \
  static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ;         \
  typedef std::integral_constant<bool,value>    type;                                                \
};


#define HAS_MEM_FUN_CALL(memfun)                                                                         \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_valid_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_valid_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  template < typename U, bool = has_mem_##memfun<U>::value >                                         \
  struct impl                                                                                        \
  {                                                                                                  \
     template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
     struct test_result { using type = yes; };                                                       \
                                                                                                     \
     template < typename V > static constexpr typename test_result<V>::type test(int);               \
     template < typename   > static constexpr                            no test(...);               \
                                                                                                     \
     static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value;                    \
     using type = std::integral_constant<bool, value>;                                               \
  };                                                                                                 \
                                                                                                     \
  template < typename U >                                                                            \
  struct impl<U,false> : std::false_type {};                                                         \
                                                                                                     \
  static constexpr bool value = impl<T>::value;                                                      \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_ambiguous_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) >                                              \
{                                                                                                     \
  struct ambiguate_seed { void memfun(...); };                                                       \
                                                                                                     \
  template < class U, bool = has_mem_##memfun<U>::value >                                            \
  struct ambiguate : U, ambiguate_seed                                                               \
  {                                                                                                  \
    using ambiguate_seed::memfun;                                                                    \
    using U::memfun;                                                                                 \
  };                                                                                                 \
                                                                                                     \
  template < class U >                                                                               \
  struct ambiguate<U,false> : ambiguate_seed {};                                                     \
                                                                                                     \
  static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_viable_mem_fun_call_##memfun;                                                              \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_viable_mem_fun_call_##memfun< T(Args...) >                                                 \
{                                                                                                     \
  static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value                   \
                             or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value;              \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_no_viable_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) >                                             \
{                                                                                                     \
  static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value;             \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct result_of_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct result_of_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  using type = decltype(std::declval<T>().memfun(std::declval<Args>()...));                          \
};

#endif


1

你可以跳过C ++ 14的所有的元编程,只是这种使用写fit::conditional飞度库:

template<class T>
std::string optionalToString(T* x)
{
    return fit::conditional(
        [](auto* obj) -> decltype(obj->toString()) { return obj->toString(); },
        [](auto*) { return "toString not defined"; }
    )(x);
}

您也可以直接从lambda创建函数:

FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional(
    [](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); },
    [](auto*) -> std::string { return "toString not defined"; }
);

但是,如果使用的编译器不支持通用lambda,则必须编写单独的函数对象:

struct withToString
{
    template<class T>
    auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
    {
        return obj->toString();
    }
};

struct withoutToString
{
    template<class T>
    std::string operator()(T*) const
    {
        return "toString not defined";
    }
};

FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
    withToString(),
    withoutToString()
);

1
编写此代码以使其不必依赖于fit标准以外的任何库有多么容易?
einpoklum

1

使用C ++ 20,您可以编写以下代码:

template<typename T>
concept has_toString = requires(const T& t) {
    t.toString();
};

template<typename T>
std::string optionalToString(const T& obj)
{
    if constexpr (has_toString<T>)
        return obj.toString();
    else
        return "toString not defined";
}

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参数的函数,该参数具有优先于long使用调用时具有的函数的优先级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
}

0

我有一个类似的问题:

可以从几个基本类派生的模板类,一些基类具有某个成员,而另一些基类则没有。

我以类似于“ typeof”(尼古拉·博内利(Nicola Bonelli's)的答案)的方式解决了问题,但是使用了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;
}

-1
template<class T>
auto optionalToString(T* obj)
->decltype( obj->toString(), std::string() )
{
     return obj->toString();
}

template<class T>
auto optionalToString(T* obj)
->decltype( std::string() )
{
     throw "Error!";
}

6
“我们不需要答案说明” ...请在您的答案中添加一些信息性说明以改善答案。谢谢。
YesThatIsMyName
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.