【问题标题】:When does type information flow backwards in C++?C++中类型信息什么时候倒流?
【发布时间】:2019-04-15 15:37:22
【问题描述】:

我刚刚在CppCon 2018 观看了 Stephan T. Lavavej 关于“类模板论证推论”的演讲,他在some point 顺便说:

在 C++ 中,类型信息几乎从不倒流……我不得不说“几乎”,因为有一两种情况,可能更多但很少

尽管我试图弄清楚他可能指的是哪些案例,但我什么也想不出来。因此问题是:

在哪些情况下 C++17 标准要求类型信息向后传播?

【问题讨论】:

  • 模式匹配部分特化和解构赋值。

标签: c++ types language-lawyer c++17 type-deduction


【解决方案1】:

这里至少有一种情况:

struct foo {
  template<class T>
  operator T() const {
    std::cout << sizeof(T) << "\n";
    return {};
  }
};

如果您执行foo f; int x = f; double y = f;,类型信息将“向后”流动以找出Toperator T 中的内容。

您可以以更高级的方式使用它:

template<class T>
struct tag_t {using type=T;};

template<class F>
struct deduce_return_t {
  F f;
  template<class T>
  operator T()&&{ return std::forward<F>(f)(tag_t<T>{}); }
};
template<class F>
deduce_return_t(F&&)->deduce_return_t<F>;

template<class...Args>
auto construct_from( Args&&... args ) {
  return deduce_return_t{ [&](auto ret){
    using R=typename decltype(ret)::type;
    return R{ std::forward<Args>(args)... };
  }};
}

所以我现在可以做

std::vector<int> v = construct_from( 1, 2, 3 );

它有效。

当然,为什么不直接使用{1,2,3}?好吧,{1,2,3} 不是表达式。

std::vector<std::vector<int>> v;
v.emplace_back( construct_from(1,2,3) );

诚然,这需要更多的魔法:Live example。 (我必须让推导返回对 F 进行 SFINAE 检查,然后使 F 对 SFINAE 友好,并且我必须在 deduce_return_t 运算符 T 中阻止 std::initializer_list。)

【讨论】:

  • 非常有趣的答案,我学到了一个新技巧,非常感谢你!我必须在make your example compile 中添加一个模板扣除指南,但除此之外,它就像一个魅力!
  • &amp;&amp; 上的限定词 operator T() 非常棒;如果在此处滥用 auto,它会导致编译错误,从而有助于避免与 auto 的不良交互。
  • 这非常令人印象深刻,您能否指出一些参考/谈谈示例中的想法?或者它是原创的 :) ...
  • @lili 哪个想法?我数 5:使用运算符 T 推断返回类型?使用标签将推导的类型传递给 lambda?使用转换运算符来滚动您自己的放置对象构造?连接所有 4 个?
  • @lili Tha“更高级的方式”示例,正如我所说,只有 4 个左右的想法粘在一起。我为这篇文章进行了即时粘合,但我当然见过很多对甚至三胞胎一起使用的。这是一堆相当晦涩难懂的技术(正如 tootsie 抱怨的那样),但没什么新奇的。
【解决方案2】:

Stephan T. Lavavej explained the case he was talking about in a tweet:

我想到的情况是,您可以获取重载/模板化函数的地址,如果它用于初始化特定类型的变量,这将消除您想要的变量的歧义。 (有一个消除歧义的列表。)

我们可以从cppreference page on Address of overloaded function 看到这样的例子,我在下面排除了一些:

int f(int) { return 1; } 
int f(double) { return 2; }   

void g( int(&f1)(int), int(*f2)(double) ) {}

int main(){
    g(f, f); // selects int f(int) for the 1st argument
             // and int f(double) for the second

     auto foo = []() -> int (*)(int) {
        return f; // selects int f(int)
    }; 

    auto p = static_cast<int(*)(int)>(f); // selects int f(int)
}

Michael Park adds:

也不限于初始化具体类型。它也可以仅从参数的数量推断

并提供this live example:

void overload(int, int) {}
void overload(int, int, int) {}

template <typename T1, typename T2,
          typename A1, typename A2>
void f(void (*)(T1, T2), A1&&, A2&&) {}

template <typename T1, typename T2, typename T3,
          typename A1, typename A2, typename A3>
void f(void (*)(T1, T2, T3), A1&&, A2&&, A3&&) {}

int main () {
  f(&overload, 1, 2);
}

我稍微详细说明了more here

【讨论】:

  • 我们也可以将其描述为:表达式类型取决于上下文的情况?
【解决方案3】:

我相信在重载函数的静态转换中,流程与通常的重载解决方案相反。所以其中之一是倒退的,我猜。

【讨论】:

  • 我相信这是正确的。当您将函数名称传递给函数指针类型时;类型信息从表达式的上下文(您分配给/构造/等的类型)向后流动到函数的名称,以确定选择哪个重载。
猜你喜欢
  • 2011-06-20
  • 2013-07-18
  • 1970-01-01
  • 2018-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-20
相关资源
最近更新 更多