【问题标题】:decltype evaluating the wrong type from an expression listdecltype 从表达式列表中评估错误的类型
【发布时间】:2023-02-01 03:51:57
【问题描述】:

在尝试 This post 的答案时, 最终得到以下代码:

#include <iostream>
#include <typeinfo>
 
 namespace Test
 {
    struct MyPtr
    {
        double operator, (int Input)
        {
            std::cout << "Operator, called" << std::endl;
            return 1.0;
        }

        MyPtr operator* ()
        {
            std::cout << "operator* called" << std::endl;
            return *this ;
        }

        MyPtr operator++ ()
        {
            std::cout << "operator++ called" << std::endl;
            return *this;
        }

        MyPtr operator!= (const MyPtr& Other)
        {
            std::cout << "operator!= called" << std::endl;
            return *this;
        }
    };

    struct Foo
    {
        MyPtr Ptr;
    };

    MyPtr begin(Foo& t)
    {
        return t.Ptr;
    }

    MyPtr end(Foo& t)
    {
        return t.Ptr;
    }
 }
 
int main()
{
    std::cout << typeid(decltype(++begin(std::declval<Test::Foo&>()),
                                 *begin(std::declval<Test::Foo&>()), 
                                  std::true_type{})).name() <<std::endl;
}

产生:

d

这里,d 来自逗号运算符。然而,由于 decltype 说明符中的最后一个表达式是 std::true_type{},为什么 decltype 说明符解析为逗号运算符返回的类型而不是 std::true 类型。

我的理论是 std::true_type 可以隐式转换为 int,此处的 operator,() 作为参数,因此 decltype 表达式等效于:

    std::cout << typeid(decltype(++begin(std::declval<Test::Foo&>()),
                                 declval<double&>())).name() <<std::endl;

你能确认这是正确的吗?

当我更好地了解编译器的行为时,我打算做什么,我计划用以下内容替换 decltype 说明符的内容:

    std::cout << typeid(decltype(void(++begin(std::declval<Test::Foo&>())),
                                 void(*begin(std::declval<Test::Foo&>())), 
                                  std::true_type{})).name() <<std::endl;

这将阻止 operator,() 匹配,即使它被覆盖。你也可以确认这是正确的吗?

【问题讨论】:

  • 通过实际评估表达式而不是仅在未评估的上下文中使用它们,您可以更容易地看到会发生什么
  • 正如那个Demo
  • 如果一个操作数是void(..),那么确实没有使用用户提供的逗号运算符。
  • 好吧,我明白为什么我的回答是错误的,但我现在无法修复它。稍后会回来。

标签: c++ c++11 typetraits decltype comma-operator


【解决方案1】:

我添加了一个成员到MyPtr 以获取更多信息。实际评估表达式会告诉您发生了什么:

#include <iostream>
#include <typeinfo>

struct MyPtr {
    int value;
    MyPtr(int x) : value(x) {}

    double operator,(int Input) {
        std::cout << "Operator, called value = " << value
                  << " Input = " << Input << std::endl;
        return 1.0;
    }
    MyPtr operator*() {
        std::cout << "operator* called value = " << value << std::endl;
        return *this;
    }
    MyPtr operator++() {
        std::cout << "operator++ called value = " << value << std::endl;
        return *this;
    }
    MyPtr operator!=(const MyPtr& Other) {
        std::cout << "operator!= called" << std::endl;
        return *this;
    }
};

struct Foo { MyPtr Ptr; };

MyPtr begin(Foo& t) { return t.Ptr; }

MyPtr end(Foo& t) { return t.Ptr; }

int main() {
    Foo f1{10};
    Foo f2{20};
    ++begin(f1), *begin(f2), std::true_type{};
    ++begin(f1), *begin(f2), std::false_type{};
}

Output:

operator++ called value = 10
operator* called value = 20
Operator, called value = 20 Input = 1
operator++ called value = 10
operator* called value = 20
Operator, called value = 20 Input = 0

你的“理论”是正确的。让我们删除++*以使其更简单,然后您可以将f1,f2,std::true_type{}视为f1.operator,( f2.operator,(std::true_type{})std::true_typeoperator boolbool 可以隐式转换为 int。为了说明,我还在代码中添加了带有false_type 的示例。

【讨论】:

  • *begin 和 ++begin 在这里(最终)创建一个特征来检查类型是否支持迭代器。我同意他们对于这个问题是多余的。但是,您的答案是 MyPtr{},MyPtr{} 是 double 是怎么来的?逗号运算符需要一个 int,而不是 MyPtr。另外,您是对的,评估的上下文更易于使用。
  • @J.M 我更新了答案
猜你喜欢
  • 2012-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多