当前位置:  开发笔记 > 编程语言 > 正文

是否可以编写模板来检查函数的存在?

如何解决《是否可以编写模板来检查函数的存在?》经验,为你挑选了14个好方法。

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

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

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

所以,如果class T已经toString()确定的话,就使用它; 否则,它没有.我不知道怎么做的神奇部分是"FUNCTION_EXISTS"部分.



1> Nicola Bonel..:

是的,使用SFINAE,您可以检查给定的类是否提供某种方法.这是工作代码:

#include 

struct Hello
{
    int helloworld() { return 0; }
};

struct Generic {};    

// SFINAE test
template 
class has_helloworld
{
    typedef char one;
    struct two { char x[2]; };

    template  static one test( typeof(&C::helloworld) ) ;
    template  static two test(...);    

public:
    enum { value = sizeof(test(0)) == sizeof(char) };
};

int main(int argc, char *argv[])
{
    std::cout << has_helloworld::value << std::endl;
    std::cout << has_helloworld::value << std::endl;
    return 0;
}

我刚用Linux和gcc 4.1/4.3测试过它.我不知道它是否可以移植到运行不同编译器的其他平台.


不需要typeof - char [sizeof(&C :: helloworld)]也可以.要避免sizeof(long)== sizeof(char),请使用struct {char [2]} ;. 它的大小必须> = 2
琐碎,但花了我一段时间才弄明白:当使用[C++ 0x](http://en.wikipedia.org/wiki/C%2B%2B0x)时,用`decltype`替换`typeof`,例如via -std =的C++ 0x.
虽然,我使用以下'one'和'two':typedef char Small; class Big {char dummy [2];}以确保没有关于平台因变量大小的歧义.
我不完全确定,但我不认为这是便携式的.typeof是GCC扩展,这不适用于其他编译器.
我怀疑它在地球上是否存在sizeof(char)== sizeof(long)的平台
确实如此,但严格来说,如果你的标准是正确的,那么就不能保证.(PS我见过这种情况的平台,但编译器无论如何都会扼杀该代码;))
decltype确实替换了C++ 0x中的typeof.但是解决方案不能用decltype编译(decltype无法解析重载函数的地址).
我重新设计了这个解决方案,所以它不需要typeof.看看我在本页底部附近的答案.实际上我所做的是合并Nicola和Johannes的答案,并尝试为所有结构提供有用的名称.希望能帮助到你.
@hrr谢谢先生,这解决了Linux for C++ 0x的编译错误.
@CashCow由于SFINAE的工作方式,在test方法上需要使用“多余的”类型名C。如果使用`T`,编译器将完全无法实例化该模板,并且代码也将无法编译(因为没有有效版本的has_helloworld <Generic>)。
如果它是一个函数模板,如何检查helloworld的存在?在这种情况下,typeof(&C :: helloworld)将失败
当helloworld超载时,这也不起作用.

2> Xeo..:

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

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

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

template
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 

template
struct sfinae_true : std::true_type{};

namespace detail{
  template
  static auto test_stream(int)
      -> sfinae_true().stream(std::declval()))>;
  template
  static auto test_stream(long) -> std::false_type;
} // detail::

template
struct has_stream : decltype(detail::test_stream(0)){};

实例.

并解释.首先,sfinae_true是一个帮助器类型,它基本上与写入相同decltype(void(std::declval().stream(a0)), std::true_type{}).优点是它更短.
接下来,取决于签入是否失败,struct has_stream : decltype(...)从任一端std::true_typestd::false_type最后继承. 最后,为您提供所传递的任何类型的"值",而无需您知道如何构建它.请注意,这只能在未评估的上下文中使用,例如,和其他.decltypetest_stream
std::declvaldecltypesizeof


请注意,decltype不一定需要,因为sizeof(并且所有未评估的上下文)都获得了增强.它只是decltype已经提供了一种类型,因此只是更清洁.这是一个sizeof重载的版本:

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

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


微软尚未在其C++编译器中实现Expression SFINAE.只是想想我可能会帮助节省一些人的时间,因为我很困惑为什么这对我不起作用.不错的解决方案,迫不及待地在Visual Studio中使用它!
如果你对decltype的两个参数感到困惑:decltype真的只需要一个; 逗号是这里的运营商.请参阅http://stackoverflow.com/questions/16044514/what-is-decltype-with-two-arguments
`decltype`优于`sizeof`的优点还在于,函数调用的特制规则不会引入临时(因此您不必拥有返回类型的析构函数的访问权限,也不会导致隐含实例化,如果返回类型是类模板实例化).
您的第一个示例链接已损坏

3> Johannes Sch..:

C++允许SFINAE用于此(请注意,使用C++ 11功能时这更简单,因为它支持几乎任意表达式上的扩展SFINAE - 以下是为了与常见的C++ 03编译器一起工作):

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

上面的模板和宏试图实例化一个模板,给它一个成员函数指针类型,以及实际的成员函数指针.如果类型不合适,SFINAE会导致模板被忽略.用法如下:

HAS_MEM_FUNC(toString, has_to_string);

template void
doSomething() {
   if(has_to_string::value) {
      ...
   } else {
      ...
   }
}

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

template
struct enable_if {
  typedef T type;
};

template
struct enable_if { };

HAS_MEM_FUNC(toString, has_to_string);

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

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

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


我喜欢用`type_check`来确保签名完全一致.有没有办法使它能够匹配任何可以调用带有签名`Sign`的方法调用的方法?(例如,如果`Sign` =`std :: string(T ::*)()`,则允许`std :: string T :: toString(int default = 42,...)`匹配.)
我只是想出一些关于这一点的东西,这对我来说并不是很明显,所以如果它有助于其他人:chk不是也不需要定义!sizeof运算符确定chk输出的大小,而不需要调用chk.
这是(或任何等效的)Boost?
@ deek0146:是的,`T`必须不是原始类型,因为指向T方法的指针不受SFINAE约束,并且对于任何非类T都会出错.IMO最简单的解决方案是组合使用`is_class`检查来自boost.
如果我的`toString`是模板化函数,我该如何才能使这个工作?

4> Morwenn..:

检测工具包

N4502提出了一种包含在C++ 17标准库中的检测功能,它可以以一种优雅的方式解决问题.而且,它刚被接受进入库基础TS v2.它介绍了一些元函数,包括requires可以用来轻松编写类型或函数检测元函数的元函数.以下是如何使用它:

template
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++ 17功能相得益彰requires:

template
using toString_t = decltype( std::declval().toString() );

template
constexpr bool has_toString = std::is_detected_v;

Boost.TTI

另一个有点惯用的工具包来执行这样的检查 - 尽管不那么优雅 - 是Boost.TTI,在Boost 1.54.0中引入.对于您的示例,您必须使用宏optionalToString.以下是如何使用它:

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

然后,您可以使用它std::is_detected来创建SFINAE检查.

说明

if constexpr生成元函数is_valid,该元函数将检查类型作为其第一个模板参数.第二个模板参数对应于成员函数的返回类型,以下参数对应于函数参数的类型.该成员has_toString包含IntegralConstant该类has_toString是否具有成员函数BOOST_TTI_HAS_MEMBER_FUNCTION.

或者,bool可以将成员函数指针作为模板参数.因此,可以替换BOOST_TTI_HAS_MEMBER_FUNCTIONhas_member_function_toString.



5> FireAphis..:

虽然这个问题已经有两年了,但我还是敢补充一下.希望它能澄清以前无可争议的优秀解决方案.我采用了Nicola Bonelli和Johannes Schaub的非常有用的答案,并将它们合并为一个解决方案,即恕我直言,更易读,更清晰,不需要typeof扩展:

template 
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  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  struct ToString
    {
        typedef void (T::*fptr)();
    };

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

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

我用gcc 4.1.2检查了它.这个功劳主要归功于Nicola Bonelli和Johannes Schaub,所以如果我的回答可以帮助你,请给他们一个投票:)


@AlastairIrvine,这个解决方案隐藏了所有内部逻辑,Konrad's给用户带来了一些负担.虽然简短且可读性更高,但Konrad的解决方案需要为每个具有"toString"的类提供单独的模板专业化.如果你编写一个通用库,希望能够使用任何类(想想类似boost),那么要求用户定义一些模糊模板的其他特化可能是不可接受的.有时候最好编写一个非常复杂的代码来保持公共接口尽可能简单.

6> Aaron McDaid..:

C++ 11的简单解决方案:

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

3年后更新:(这是未经测试的).为了测试存在,我认为这将有效:

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


这很简单而且很优雅,但严格来说并不能回答OP的问题:你不能让调用者_check_来实现函数的存在,你总是_provide_它.但不管怎样,还不错.

7> Konrad Rudol..:

这是什么类型的特征.不幸的是,它们必须手动定义.在您的情况下,请想象以下内容:

template 
struct response_trait {
    static bool const has_tostring = false;
};

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


Comptrol:不,引用的段落不适用于此,因为整数类型的静态常量是特殊情况!它们的行为*完全*就像这里的枚举一样,是首选的方式.只有那些不符合C++标准的编译器才需要旧的enum hack.
你应该更喜欢enum for traits而不是静态常量:"静态常量成员是lvalues,它强制编译器实例化并为静态成员分配定义.因此,计算不再局限于纯粹的"编译时"效果."
"枚举值不是左值(也就是说,它们没有地址).因此,当你"通过引用"传递它们时,不会使用静态内存.这几乎就像你将计算值作为文字传递一样这些考虑因素促使我们使用枚举值"C++模板:完整指南"
+1:特质一直是解决问题的干净方法.
@Roger Pate:不太好."在程序中使用"这里显然是"引用"的同义词.这篇文章的主流阅读,以及所有现代C++编译器实现的那一段,都是你可以得到一个静态常量的*值*,而不需要声明它(上一句话说:"......成员可以出现在积分中常数表达式......").如果你采用它的地址(通过`&T :: x`显式地或通过将它绑定到引用隐式地),你*只需要定义它.
[这是一个如何在C++ 11中使用`type traits`进行条件编译的例子](http://stackoverflow.com/questions/13787490/how-do-you-use-type-traits-to-do -conditional编译/ 13787531)

8> akim..:

好吧,这个问题已经有很多答案了,但我想强调一下Morwenn的评论:有一个C++ 17的提案让它变得非常简单.有关详细信息,请参阅N4502,但作为一个独立的示例,请考虑以下内容.

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

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

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

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

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

template 
using toString_t = decltype(std::declval().toString());

template 
using has_toString = detect;

以下示例取自N4502,显示了更精细的探测:

// Archetypal expression for assignment operation.
template 
using assign_t = decltype(std::declval() = std::declval())

// Trait corresponding to that archetype.
template 
using is_assignable = detect;

与上面描述的其他实现相比,这个实现相当简单:减少了一组工具(void_tdetect)就足够了,不需要毛茸茸的宏.此外,据报道(见N4502),它比以前的方法更有效(编译时和编译器内存消耗).

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

#if __GNUC__ < 5 && ! defined __clang__
// /sf/ask/17360801/
template 
struct voider
{
  using type = void;
};
template 
using void_t = typename voider::type;
#else
template 
using void_t = void;
#endif



9> Brett Rossie..:

以下是一些使用片段:*所有这些的胆量更远

检查x给定类中的成员.可以是var,func,class,union或enum:

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_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::value;

检查成员变量x:

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x::value;

检查会员类x:

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x::value;

检查成员联盟x:

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x::value;

检查成员枚举x:

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_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::value;

要么

CREATE_MEMBER_CHECKS(x);  //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_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  struct ambiguate : public Args... {};

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

template
struct got_type : std::false_type {};

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

template
struct sig_check : std::true_type {};

template
struct has_member {
    template static char ((&f(decltype(&C::value))))[1];
    template static char ((&f(...)))[2];

    //Make sure the member name is consistently spelled the same.
    static_assert(
        (sizeof(f(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(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                             \
struct Alias_##member;                                                      \
                                                                            \
template                                                        \
struct Alias_##member <                                                     \
    T, std::integral_constant::value>  \
> { static const decltype(&T::member) value; };                             \
                                                                            \
struct AmbiguitySeed_##member { char member; };                             \
                                                                            \
template                                                        \
struct has_member_##member {                                                \
    static const bool value                                                 \
        = has_member<                                                       \
            Alias_##member>            \
            , Alias_##member                        \
        >::value                                                            \
    ;                                                                       \
}

CREATE_MEMBER_VAR_CHECK:

//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name)                                   \
                                                                            \
template                             \
struct has_member_var_##var_name : std::false_type {};                      \
                                                                            \
template                                                        \
struct has_member_var_##var_name<                                           \
    T                                                                       \
    , std::integral_constant<                                               \
        bool                                                                \
        , !std::is_member_function_pointer::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                             \
struct has_member_func_##templ_postfix : std::false_type {};                \
                                                                            \
template                                                        \
struct has_member_func_##templ_postfix<                                     \
    T, std::integral_constant<                                              \
        bool                                                                \
        , sig_check::value                         \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_CLASS_CHECK:

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

CREATE_MEMBER_UNION_CHECK:

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

CREATE_MEMBER_ENUM_CHECK:

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

CREATE_MEMBER_FUNC_CHECK:

//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func)          \
template                            \
struct has_member_func_##func {                 \
    static const bool value                     \
        = has_member_##func::value           \
        && !has_member_var_##func::value     \
        && !has_member_class_##func::value   \
        && !has_member_union_##func::value   \
        && !has_member_enum_##func::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)



10> Yakk - Adam ..:

对于一般问题,这是一个C++ 11解决方案,如果"如果我做了X,它会编译吗?"

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

性状has_to_string使得has_to_string::valuetrue当且仅当T有一个方法.toString可以与在此上下文0参数调用.

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

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

这比复杂的SFINAE表达更容易维护.

如果你发现自己做了很多,你可以用宏写这些特征,但它们相对简单(每行几行),所以可能不值得:

#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template struct TRAIT_NAME : std::false_type {}; \
template 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().toString() )

创建上述特征类.

顺便说一句,上述技术是MS称之为"表达SFINAE"的一部分,他们的2013编译器失败了.

请注意,在C++ 1y中,可以使用以下语法:

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

这是一个滥用大量C++特性的内联编译条件分支.这样做可能是不值得的,因为(代码是内联的)的好处不值得花费(没有人理解它是如何工作的),但是上述解决方案的存在可能是有意义的.



11> Michael Burr..:

现在这是一个不错的小谜题 - 很棒的问题!

这是Nicola Bonelli解决方案的替代方案,不依赖于非标准typeof操作员.

遗憾的是,它不适用于GCC(MinGW)3.4.5或Digital Mars 8.42n,但它适用于所有版本的MSVC(包括VC6)和Comeau C++.

较长的注释块具有关于它如何工作(或应该工作)的详细信息.正如它所说,我不确定哪种行为符合标准 - 我欢迎对此进行评论.


更新 - 2008年11月7日:

看起来虽然这段代码在语法上是正确的,但MSVC和Comeau C++显示的行为并不符合标准(感谢Leon Timmermans和litb指向我正确的方向).C++ 03标准说明如下:

14.6.2从属名称[temp.dep]

第3段

在类模板的定义或类模板的成员中,如果类模板的基类依赖于模板参数,则在类的定义时,在非限定名称查找期间不会检查基类作用域.模板或成员或在类模板或成员的实例化期间.

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

GCC和数字火星的行为看起来是正确的 - 在这两种情况下,非成员toString()函数都绑定到调用.

老鼠 - 我以为我可能找到了一个聪明的解决方案,而是发现了一些编译器错误......


#include 
#include 

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 
    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 
std::string optionalToString(T & obj)
{
    // ugly, hacky cast...
    optionalToStringImpl* temp = reinterpret_cast*>( &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;
}


我不确定为什么它没有添加我的另一个评论:你的toString调用是不合格的.所以它总是调用自由函数而不是基类中的函数,因为基类依赖于模板类型参数.

12> 小智..:

如果方法碰巧在基类中定义,则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中,我注意到如果使用没有参数的方法,则需要在argments周围插入一对额外的redundant()以在sizeof表达式中推导出().



13> nob..:

MSVC具有__if_exists和__if_not_exists关键字(Doc).与Nicola的SFINAE类型一起,我可以像OP一样为GCC和MSVC创建一个检查.

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



14> kispaljr..:

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

SFINAE检查继承的成员函数

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

例1:

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

template struct has_const_begin
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template 
    static Yes test(U const * data, 
                    typename std::enable_ifbegin())
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference::type*)0));
};

请注意,它甚至检查方法的常量,并使用原始类型.(我的意思has_const_begin::value是假,不会导致编译时错误.)

例2

现在我们正在寻找签名: void foo(MyClass&, unsigned)

template struct has_foo
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template
    static Yes test(U * data, MyClass* arg1 = 0,
                    typename std::enable_iffoo(*arg1, 1u))
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference::type*)0));
};

请注意,MyClass不必是默认构造或满足任何特殊概念.该技术也适用于模板成员.

我急切地等待着这个意见.

推荐阅读
Gbom2402851125
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有