使用decltype()和SFINAE的错误

时间:2010-11-09 18:17:18

标签: c++ c++11 sfinae decltype

为了回应某些其他问题,我写了这段代码。

struct no_type{};
template<typename T> struct has_apply {
    static decltype(T().apply<0u>(double())) func( T* ptr );
    static no_type func( ... );
    static const bool result = !std::is_same<no_type, decltype(func(nullptr))>::value;
};

class A {
public:
    template< unsigned n >
    void apply( const double& );

};
class B {
};

int main()
{
  std::cout << std::boolalpha << has_apply< A >::result << '\n';
  std::cout << std::boolalpha << has_apply< B >::result << '\n';
  std::cin.get();
  return( 0 );
}

现在在我看来,如果T提供接受双rvalue和模板参数文字的非静态成员函数“apply”,则结果应为true,否则为false。但是,在编译has_apply<B>时,给出的示例实际上无法为B类编译。不应该在decltype语句中替换T失败的事实意味着它只是调用另一个函数吗?这不是SFINAE的那一点吗?

以最荒谬,毫无意义的方式解决:

struct no_type{};
template<typename T> struct has_apply {
    template<typename U> static decltype(U().apply<0u>(double())) func( U* );
    template<typename U> static no_type func( ... );
    static const bool result = !std::is_same<no_type, decltype(func<T>(nullptr))>::value;
};

class A {
public:
    template< unsigned n >
    void apply( const double& );

};
class B {
};

int main()
{
  std::cout << std::boolalpha << has_apply< A >::result << '\n';
  std::cout << std::boolalpha << has_apply< B >::result << '\n';
  std::cin.get();
  return( 0 );
}

2 个答案:

答案 0 :(得分:3)

SFINAE适用于函数模板的模板参数替换失败,而不适用于具有(非模板)函数作为成员的类模板的模板参数,就像您的情况一样。

修复后,您至少应将decltype(T().apply<0u>(double()))更改为decltype(T().template apply<0u>(double())),因为T()表达式属于依赖类型。原因是:当编译器第一次看到T().apply<0u>时,它对T一无所知,那么它应如何解析标记apply<.之后? apply可能是成员模板,然后<将为其启动参数列表。 OTOH apply可能是非模板成员(例如数据成员),然后<将被解析为'less-than'运算符。存在歧义,编译器现在解决这个问题还为时尚早。程序员可以使用消歧机制来告诉编译器预期apply是什么:模板与否。这里有.template(或->template::template)构造来拯救:如果它存在,编译器知道它应该是模板成员,否则如果它不存在那么编译器知道该成员不应该是模板。

最后,这是我创建的一个正常工作的示例,并使用-std=c++0x在g ++ 4.5.0上生成所需的结果:

#include <iostream>

template < class T >
decltype( T().template apply< 0u >( double() ) ) f( T &t )
{
    return t.template apply< 0u >( 5. );
}

const char *f( ... )
{
    return "no apply<>";
}

class A {
public:
    template < unsigned >
    int apply( double d )
    {
        return d + 10.;
    }
};

class B {};

int main()
{
    A a;
    std::cout << f( a ) << std::endl;
    B b;
    std::cout << f( b ) << std::endl;
}

输出是:

15
no apply<>

现在,如果从第一个.template定义中删除f(),则输出变为:

no apply<>
no apply<>

这表示class A的替换失败,因为它没有任何名为apply非模板成员。 SFINAE在行动!

答案 1 :(得分:0)

很抱歉发帖作为答案,但评论似乎对我来说是蠢蠢的。

我见过人们评论declval(),但不需要。可以简单地写

而不是T()
 decltype( *(T*)0->template apply< 0u >( double() ) ) )

有效,因为它只出现在decltype内部,并且实际上并未在运行时进行评估。另一种选择是声明

T T_obj( void );

然后使用

decltype( T_obj().template apply< 0u >( double() ) ) )

作为旁注,我记得读过Stroustrup设计这种语言,因为他不想用错误的工具做一份工作。 C ++不是元编程的错误语言吗?

虽然c ++ 0x改进了很多东西,但这似乎不是焦点。有没有像c ++那样“接近金属”的其他语言,它提供了更好的工具来编写将在编译时生成代码的元代码?