什么时候类型信息在C ++中向后流动?

时间:2018-11-12 21:16:39

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

我刚刚在CppCon 2018上观看了斯蒂芬·拉瓦维(Stephan T. Lavavej)关于“类模板参数推论”的演讲,他在some point上偶然地说:

  

在C ++类型的信息中,信息几乎永远不会倒退... 我不得不说“几乎”是因为有一种或两种情况,可能更多但很少。

尽管试图弄清楚他可能指的是哪种情况,但我什么也没想。因此,问题是:

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

3 个答案:

答案 0 :(得分:78)

至少有一种情况:

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

如果您执行foo f; int x = f; double y = f;,则类型信息将“向后流动”以找出T中的operator 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。 (我必须让演绎return对F进行SFINAE检查,然后使F对SFINAE友好,并且我必须在deduce_return_t运算符T中阻止std :: initializer_list。)

答案 1 :(得分:30)

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

答案 2 :(得分:19)

我相信在静态转换重载函数的过程中,流的方向与通常的重载分辨率相反。我想其中之一就是倒退。