【问题标题】:false behaviour of is_base_of when used together with bindis_base_of 与 bind 一起使用时的错误行为
【发布时间】:2011-08-19 14:58:26
【问题描述】:

将可变参数模板参数与一个简单的模板参数结合使用,我在 is_base_ofbinded 仿函数实例化时遇到了一些奇怪的行为。

代码如下:

template <class T, class Index>
class Base{};

template<typename T>
struct Checker {
    typedef int result_type;

    // Returns 1 if a given T type is descendant of Base<T,First>
    template<typename First, typename ...Args>
    result_type operator()(First&& first, Args&&... params)
    {
        return check(std::is_base_of<Base<T,First>, T>(),
                std::forward<First>(first),
                std::forward<Args>(params)...);
    }
    template<typename ...Args>
    result_type check(const std::true_type&, Args&&... params)
    {
        return 1;
    }
    template<typename ...Args>
    result_type check(const std::false_type&, Args&&... params)
    {
        return 0;
    }
};

struct A {};
struct B : Base<B,int> {};

int main()
{
    Checker<A> ch1;
    std::cout<<ch1(3.14)<<std::endl;
    Checker<B> ch2;
    std::cout<<ch2(1 ,3.14)<<std::endl; // output is 1
    std::cout<<std::bind(ch2, 1, 3.14)()<<std::endl; // output is 0 but it should be 1 !
    return 0;
}

程序输出为:

0
1
0

但我希望:

0
1
1

我是否以错误的方式使用可变参数模板?有没有其他(正确的)方法来获得像 Args 这样的可变参数类型列表的第一种类型?为什么只有与绑定表达式一起使用时才会出现问题?

注意,如果我将 Base 模板修改为只有一个模板参数,则绑定表达式有效:

template <class T>
class Base{};

template<typename T>
struct Checker {
    typedef int result_type;

    // Returns 1 if a given T type is descendant of Base<T>
    template<typename ...Args>
    result_type operator()(Args&&... params)
    {
        return check(std::is_base_of<Base<T>, T>(),
                std::forward<Args>(params)...);
    }
    template<typename ...Args>
    result_type check(const std::true_type&, Args&&... params)
    {
        return 1;
    }
    template<typename ...Args>
    result_type check(const std::false_type&, Args&&... params)
    {
        return 0;
    }
};

struct A {};
struct B : Base<B> {};

int main()
{
    Checker<A> ch1;
    std::cout<<ch1(3.14)<<std::endl;
    Checker<B> ch2;
    std::cout<<ch2(3.14)<<std::endl; // output is 1
    std::cout<<std::bind(ch2, 3.14)()<<std::endl; // output is 1 this time!
    return 0;
}

【问题讨论】:

  • 请问您为什么将其实现为函数而不是元函数?当所有检查都可以在编译时完成时,涉及一个值似乎完全没有必要。

标签: c++11 bind variadic-templates typetraits


【解决方案1】:

您没有得到预期的输出,因为在std::bind() 之后调用时,Checker 函数对象中First 的数据类型是int&amp; 类型,而不是int

因此std::is_base_of&lt;Base&lt;B,int&amp;&gt;, B&gt; 不会实例化为std::true_type 以调用Checker::check

问题在于std::bind 正在创建一个对象,该对象在内部存储您传递给它的函数的参数。因此,有一个命名的左值作为std::bind 返回的对象的非静态数据成员,它保存您作为参数传递的值,以绑定到您的函数。当该非静态数据成员在您调用仿函数的operator() 时传递给右值引用时,它作为左值引用传递,因为它不再是临时对象。如果您执行以下操作,您会遇到类似的问题:

int x = 1;
Checker<B> ch2;
std::cout<<ch2(x, 3.14)<<std::endl;

命名值x 是一个左值,并将作为左值引用而不是临时值传递给first 方法中的first 参数,因为first 是一个r - 价值参考。因此,您的类型将再次以int&amp; 而不是int 结束,并且您将打印0 的值。

要解决此问题,您可以执行以下操作:

template<typename First, typename ...Args>
result_type operator()(First&& first, Args&&... params)
{
   if (std::is_reference<First>::value)
    {
        return check(std::is_base_of<Base<T, typename std::remove_reference<First>::type>, T>(),
            std::forward<First>(first),
            std::forward<Args>(params)...);
    }
    else
    {
        return check(std::is_base_of<Base<T,First>, T>(),
            std::forward<First>(first),
            std::forward<Args>(params)...);
    }
}

这将剥离对象的引用类型并为您提供所需的结果。

【讨论】:

  • 是的,确实如此。但是有没有办法以某种方式将它作为 int 而不是 int& 传递?
  • 为什么以及如何变成 int& 而不是 int ?
  • 谢谢 Jason,它帮了很多忙。
【解决方案2】:

不幸的是 std::is_reference 在更复杂的问题上没有给我预期的结果。 所以最后我选择提供引用和常量引用重载:

template<typename First, typename ...Args>
result_type operator()(First& first, Args&&... params)
{
    return check(std::is_base_of<Base<T,First>, T>(),
            first,
            std::forward<Args>(params)...);
}
template<typename First, typename ...Args>
result_type operator()(const First& first, Args&&... params)
{
    return check(std::is_base_of<Base<T,First>, T>(),
            first,
            std::forward<Args>(params)...);
}

【讨论】:

  • 我很想知道,意外的结果是什么?
  • 顺便说一句,请记住 const 左值引用不会绑定到非常量 rvale 引用,因此如果没有该类型的特定重载,您将无法传递 const 左值参数导致编译器错误。只有常量右值引用可以绑定任何东西。
猜你喜欢
  • 2020-09-09
  • 1970-01-01
  • 2020-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-28
  • 2015-02-06
  • 1970-01-01
相关资源
最近更新 更多