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


92

我刚刚看了斯蒂芬·拉瓦维(Stephan T. Lavavej)在CppCon 2018“类模板参数推论”上的讲话,在某个时候他偶然说:

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

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

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


模式匹配部分专业化和解构分配。
v.oddou

Answers:


80

这是至少一种情况:

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。(我必须让ducuce return对F进行SFINAE检查,然后使F对SFINAE友好,并且我必须在deduce_return_t运算符T中阻止std :: initializer_list。)


答案非常有趣,我学到了一个新技巧,非常感谢!我必须添加一个模板推导准则,以使您的示例得以编译,但除此之外,它就像一个魅力一样!
Massimiliano

5
上的&&预选赛operator T()很棒。auto如果auto在此处使用不当,则会导致编译错误,从而避免与之交互不良。
贾斯汀

1
这是非常令人印象深刻的,您能指出我在示例中的一些想法吗?或也许是原始的:) ...
llllllllll

3
@lili哪个主意?我数5:使用运算符T推断返回类型吗?使用标签将推导的类型传递给lambda?使用转换运算符来构建自己的放置对象?连接所有4个吗?
Yakk-Adam Nevraumont

1
如我所说,@ lili Tha“更高级的方式”示例只是将大约4个想法粘合在一起。我为这篇文章进行了即时粘贴,但我当然已经看到许多成对甚至三胞胎一起使用。这是一堆相当晦涩的技术(就像tootsie抱怨的那样),但没有什么新颖的。
Yakk-Adam Nevraumont

31

斯蒂芬·拉瓦维(Stephan T.Lavavej)在推文中解释了他所谈论的案件

我在想的情况是,您可以在其中获取重载/模板化函数的地址,并且如果该地址用于初始化特定类型的变量,则可以消除想要的歧义。(下面列出了一些可消除歧义的内容。)

我们可以在重载函数的地址上的cppreference页面上看到此示例,以下是我的一些例外:

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)补充说

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

并提供此实时示例

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

在这里详细说明一下


4
我们也可以将其描述为:表达式的类型取决于上下文的情况?
MM

20

我相信在对重载函数进行静态转换时,流程的方向与通常的重载解析相反。我想其中之一就是倒退。


7
我相信这是正确的。这是当您将函数名称传递给函数指针类型时。类型信息从表达式的上下文(您要分配给/构造/等的类型)向后流到函数的名称,以确定选择哪个重载。
Yakk-Adam Nevraumont
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.