何时类型信息在C++中向后流动?

Mas*_*ano 90 c++ types language-lawyer type-deduction c++17

我只是看着Stephan T. Lavavej谈论CppCon 2018"课程模板论证演绎",他在某些时候偶然说:

在C++类型中,信息几乎从不向后流动...... 我不得不说"差不多",因为有一两个案例,可能更多但很少.

尽管试图找出他可能指的是哪些案件,但我无法想出任何建议.因此问题是:

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

Yak*_*ont 79

这里至少有一个案例:

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

如果你这样做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)... };
  }};
}
Run Code Online (Sandbox Code Playgroud)

所以现在我能做到

std::vector<int> v = construct_from( 1, 2, 3 );
Run Code Online (Sandbox Code Playgroud)

它的工作原理.

当然,为什么不{1,2,3}呢?好吧,{1,2,3}不是表达.

std::vector<std::vector<int>> v;
v.emplace_back( construct_from(1,2,3) );
Run Code Online (Sandbox Code Playgroud)

诚然,这需要更多的魔法:实例.(我必须使演绎回报做f的SFINAE检查,然后进行F为SFINAE友好,而且我必须阻止的std ::在deduce_return_t运营商T. initializer_list)

  • `运算符T()`上的`&&`限定符是一个很好的接触; 如果在这里滥用"auto",它会导致编译错误,从而有助于避免与"auto"的不良交互. (5认同)
  • @lili有哪些想法?我算5:用运算符T推导出返回类型?使用标签将推导出的类型传递给lambda?使用转换运算符滚动自己的放置对象构造?连接所有4? (3认同)

Sha*_*our 30

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)
}
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

在这里详细说明了一点.

  • 我们还可以将其描述为:表达式的类型取决于上下文的情况? (4认同)

jba*_*ple 19

我相信静态转换过载函数,流程与通常的重载分辨率相反.所以其中一个是倒退的,我猜.

  • 我相信这是正确的.当你将函数名传递给函数指针类型时; 类型信息从表达式的上下文(您指定给/ construct/etc的类型)向后流入函数的名称,以确定选择哪个重载. (6认同)