2

有没有办法在 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()

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

4

6 回答 6

9

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

static_assert假设 C++0x,您应该使用 强制执行您的要求。如果您使用的是 C++03,是否应该使用static_assert(例如 Boost's 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,该库带有一系列有用的特征,您可以直接使用这些特征,而无需编写离线代码。更有趣的是,trait 的使用不仅限于编写约束,它们还可以与 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为此表达式选择的任何重载的结果类型)是否可转换为类型(不要检查它是否该类型;这太严格了,没有充分的理由)。然而,这会夸大特征,再次使这有利于约束类。

I do not know of a way to make a static_assert part of the signature of a function template (this seems to be something you want), but it can appear inside a class template. Boost.ConceptCheck doesn't support that either.

于 2011-08-28T18:03:03.220 回答
5

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

概念是对最新 C++ 标准的提议补充,但由于委员会不相信它们足够稳固以包含在语言中而被放弃。看看 Herb Sutter 写的关于他们被排除在标准之外的内容。

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

于 2011-08-28T16:57:27.803 回答
2

BoostBOOST_STATIC_ASSERT为此提供优惠。static_assert刚刚获得批准的 C++ 标准版本将提供宏的内置版本。

enable_if,另一方面,不太适合这个。它可以使用,但主要目的enable_if是区分其他模棱两可的重载。

于 2011-08-28T17:41:24.780 回答
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()

这就是你要求的对吗?

于 2011-08-28T16:56:37.707 回答
0

您不必做任何事情——只需从您的示例中省略假设的“哪里......存在”,它是正常的 C++ 代码。

如果您坚持让该功能仅在某些条件下可用,您可以尝试从这里boost::enable_if结合:http: //lists.boost.org/Archives/boost/2002/03/27229.phphas_member

这个想法是,如果满足某些条件,您将只允许实例化模板函数......但是由于SFINAE编译器基本上会在条件与实际编译相同的情况下为您执行此操作-该功能的时间需求(如您的示例中所示)。

于 2011-08-28T16:56:29.780 回答
0

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

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

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

于 2011-08-28T17:06:52.597 回答