C ++编译时鸭子用接口打字

时间:2011-08-28 16:45:37

标签: c++ templates duck-typing

有没有办法在C ++中执行以下操作

template<typename TAnimal>
bool can_eat(TAnimal& animal) where bool TAnimal::IsAlive() exists
{
    return !animal.IsAlive();
}

//...

Duck duck;
assert(can_eat(duck) == true); //compiles

Wood wood;
can_eat(wood); // fails to compile because wood doesn't have IsAlive()

在我看来,显式界面使得函数所期望的更清晰。但是,我不想创建一个实际的接口类(非常乏味)。

6 个答案:

答案 0 :(得分:8)

使用enable_if来强制执行您的要求。使用enable_if会使函数“消失”,这对用户来说可能非常混乱。典型症状是错误消息,例如error: no matching function for call to expression。这并不能完全向用户传达违反要求的情况。

您应该使用static_assert来强制执行您的要求,假设C ++为0x。如果您正在使用C ++ 03,那么您是否应该使用static_assert的仿真(例如Boost的STATIC_ASSERT)是一种折腾,因为这通常意味着为另一个交换一条错误消息

对比:

// SFINAE for types that do not decay to int
template<
    typename T
    , typename = typename std::enable_if<
        std::is_same<
            typename std::decay<T>::type
            , int
        >::value
    >::type
>
void
f(T&&)
{}

// using static assert instead
template<
    typename T
>
void
g(T&&)
{
    static_assert( std::is_same<typename std::decay<T>::type, int>::value
                 , "Constraints violation" );
}

使用GCC我在执行f("violation")时收到以下错误(两条消息都带有文件名和行号):

error: no matching function for call to 'f(const char [10])'

另一方面,g("violation")产生:

error: static assertion failed: "Constraints violation"

现在假设您在断言中使用明确的显式消息,例如模板foo: parameter type must be CopyConstructible中的foo

话虽如此,SFINAE和static_assert有点对立,因此明确的约束违规消息和聪明的重载并不总是可行和/或容易。


使用Boost.ConceptCheck可以轻松实现您想要做的事情。然而,它需要编写外部代码:约束类。我也不认为它在可用的地方使用static_assert,因此错误消息可能不太好。这可以在将来改变。

另一种可能性是使用static_assert +类型特征。这种方法的有趣之处在于,使用C ++ 0x,库中会提供一系列有用的特性,您可以直接使用这些特性,而无需编写外部代码。更有趣的是,特征的使用不仅限于编写约束,它们也可以与SFINAE一起使用以实现聪明的重载。

但是,没有可用的特性来检查类型是否支持特定的操作成员,这可能是由于C ++处理函数名称的方式。我们不能使用has_member<T, &T::member_to_test_for>这样的东西,因为只有当我们测试的成员存在于第一位时才会有意义(忽略重载之类的事情以及我们还需要传递成员签名的事实)特质)。

以下是将任意表达式转换为特征的方法:

template<typename T>
struct void_ {
    typedef void type;
};

template<typename T>
struct trait {
private:
    typedef char yes[1];
    typedef char no[2];

    template<typename U>
    static
    yes&
    test(U&&
        , typename void_<decltype( std::declval<U&>().member() )>::type* = 0);

    static
    no&
    test(...);

public:
    static constexpr bool value = sizeof test(std::declval<T>()) == sizeof(yes);
};

请注意这是多么大小。编写Boost.ConceptCheck约束类可能更容易(但请记住,不能为SFINAE重用)。

任意表达式为std::declval<U&>().member()。这里的要求是给定左值引用U(或T对于特征为真的情况,如果你愿意的话),那么在它上面调用member()是有效的。 / p>

您还可以检查该表达式的类型(即为此表达式选择了member的任何重载的结果类型)是否可转换为类型(不检查是否为< / em>那种类型;由于没有充分的理由,这种限制太多了)。然而,这会使特征膨胀,再次使这有利于约束类。

我不知道如何制作函数模板签名的static_assert部分(这似乎是你想要的东西),但它可以出现在类模板中。 Boost.ConceptCheck也不支持。

答案 1 :(得分:5)

这是概念旨在解决的问题。

概念是对最新C ++标准的建议添加,但被删除是因为委员会不相信它们足够坚实以包含在语言中。 See what Herb Sutter wrote关于将他们排除在标准之外。

从技术上讲,概念是不必要的,因为模板只是使用它们可能的任何东西(即丢失where子句,并且你有你想要的东西)。如果在编译时没有所需的方法,那么代码就不会编译。但是概念给编码器更明确的控制类型的接口,并且会提供比大多数编译器当前提供的更合理的错误消息。

答案 2 :(得分:2)

Boost为此提供了BOOST_STATIC_ASSERT。刚刚批准的C ++标准版本将提供static_assert宏的内置版本。

另一方面,

enable_if并不适合这一点。 可以使用,但enable_if的主要目的是区分其他模糊的重载。

答案 3 :(得分:1)

where void TAnimal::IsAlive() exists

我认为你的意思是bool TAnimal::IsAlive()?如果是这样,C ++已经完成了你的要求。如果Duck有IsAlive()方法,那么这将编译:

Duck duck;
assert(can_eat(duck) == true); //compiles

如果Wood没有IsAlive()方法,则无法编译:

Wood wood;
can_eat(wood); // fails to compile because wood doesn't have IsAlive()

这就是你要求的权利吗?

答案 4 :(得分:0)

你不需要做任何事情 - 只是省略你的例子中假设的“where ... exists”,这是正常的C ++代码。

如果您坚持只在某些条件下才能使用该功能,您可以尝试将boost::enable_if与此处的has_member合并:http://lists.boost.org/Archives/boost/2002/03/27229.php

这个想法是,如果满足某些条件,你只允许实例化模板函数...但是因为SFINAE编译器基本上会在条件为的情况下为你做这个与函数的实际编译时需求相同(如示例所示)。

答案 5 :(得分:0)

正如其他人所说,这将起作用。如果函数不存在,它将无法实例化模板。

boost库包含一些类来帮助处理这类事情,例如enable_if,它只能用于“启用”条件为真的模板。还有type traits库是相关的,您可以使用this在编译时确定您要调用的函数是否存在。

我不得不承认我自己没有使用过这些,但在我看来,你应该能够用它来实现你想要的......