C++ 编译时鸭子类型(duck typing)接口(interface)类型

标签 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()

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

最佳答案

使用enable_if执行您的要求。使用 enable_if将使功能“消失”,这可能会让用户感到困惑。典型症状是错误消息,例如 error: no matching function for call to <i>expression</i> .这并不能准确地向用户传达违反了要求。

您应该改为使用 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 库附带了一系列有用的特性,您可以开箱即用,无需编写外联代码。更有趣的是,traits 的使用不仅限于写约束,它们还可以与 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()上是有效的。

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

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

关于C++ 编译时鸭子类型(duck typing)接口(interface)类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7222309/

相关文章:

c++ - 混合 MFC 和 STL

c++ - 特化此函数模板时不能省略模板参数

javascript - 考虑到工厂功能和序列化的对象类型推荐方法

ruby-on-rails - 当你说 Ruby 是反光的时候,这主要是指 "duck typing"吗?

c++ - Winsock 程序收到 400 错误请求,您的浏览器发送了该服务器无法理解的请求

c# - 从 C++ 升级到 C#

java - 通用 java 函数使用的 "Class type"的 "type parameter"

templates - 如何从 template.FuncMap 返回 HTML 模板?

c++ - 递归的可变参数模板函数

c# - 鸭子类型(duck typing)在 C# 编译器中键入