与绑定一起使用时 is_base_of 的错误行为

发布于 2024-11-30 14:00:17 字数 2551 浏览 4 评论 0 原文

将可变参数模板参数与简单模板参数一起使用时,当从绑定函子实例化is_base_of时,我遇到了一些奇怪的行为。

这是代码:

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 这样的可变参数类型列表的第一种类型?为什么只有与绑定表达式一起使用时才会出现问题?

请注意,如果我将基本模板修改为只有一个模板参数,则绑定表达式将起作用:

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;
}

Using variadic template arguments together with a simple template argument I have experienced some strange behaviour of is_base_of when it was instantiated from a binded functor.

Here is the code:

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;
}

The program output is:

0
1
0

But I would expect:

0
1
1

Am I using the variadic templates in a wrong way? Is there any other (correct) way to get the first type of a variadic type list like Args? Why this is a problem only when it is used with the bind expression?

Note, if I am modifing the Base template to have only one template parameter, then the bind expression works:

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;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

难忘№最初的完美 2024-12-07 14:00:17

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

因此,std::is_base_of, B> 不会实例化为 std::true_type 来调用 Checker::检查

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

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

命名值 x 是一个左值,并将被传递给 中的 first 参数operator() 方法作为左值引用,而不是临时的,因为 first 是右值引用。因此,您的类型最终将再次成为 int& 而不是 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)...);
    }
}

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

You're not getting the expected output because the data-type of First in your Checker function object when called after std::bind() is of type int&, not int.

Therefore std::is_base_of<Base<B,int&>, B> does not instantiate to a std::true_type for the call to Checker::check.

The problem is that std::bind is creating an object that internally stores the arguments for the function you are passing to it. Therefore there is a named l-value as a non-static data-member of the object returned by std::bind that is holding the value you passed as an argument to be bound to your function. When that non-static data-member is then passed to the r-value reference at the time you call the operator() of the functor, it's passed as an l-value reference, since it is no longer a temporary object. You would have a similar problem if you did something like:

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

The named-value x is an l-value, and would be passed to the first argument in your operator() method as an l-value reference, not as temporary, since first is a r-value reference. Therefore your type would end up again as an int& and not an int, and you'd print a value of 0.

To fix this problem, you can do something like:

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)...);
    }
}

This will strip off the reference-type of the object and give you the results you want.

追星践月 2024-12-07 14:00:17

不幸的是 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)...);
}

Unfortunately std::is_reference did not give me the expected result on a more complicated issue.
So finally I choosed providing the reference and const-reference overloads:

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)...);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文