1

背景:

C++17 有两个很棒的特性:聚合初始化模板类型推导(针对类)。聚合初始化允许您在不复制或移动字段的情况下实例化字段,并且模板类型推导使您不必指定参数的类型。

下面Wrapper代码中的类就是一个例子。只要HAVE_MOVE_AND_COPY未定义,它就有聚合初始化,但是模板类型推导不起作用。

另一方面,如果定义了HAVE_MOVE_AND_COPY 则模板类型推导有效,但聚合初始化中断。我怎么能两者兼得?

#include <cstdio>
#include <utility>

template<class T> 
struct Wrapper {
    T value;
   #ifdef HAVE_MOVE_AND_COPY
    Wrapper(T const & val) : value{val} {}
    Wrapper(T && val) : value{std::move(val)} {}
   #endif
    Wrapper(Wrapper const &) = default;
    Wrapper(Wrapper &&) = default;
    

};

struct VocalClass {
    VocalClass() { puts("VocalClass()"); }
    VocalClass(VocalClass const&) { puts("VocalClass(VocalClass const &)"); }
    VocalClass(VocalClass &&) { puts("VocalClass(VocalClass &&)"); }
};

int main() {
    Wrapper<VocalClass> w { VocalClass() }; 

   #ifdef TRY_DEDUCTION
    Wrapper w2 { VocalClass() }; 
   #endif
}

例子:

没有发生移动或复制,但您没有模板扣除:

$ c++ -std=c++17 example.cc && ./a.out
VocalClass()

有模板扣除,但VocalClass被移动:

$ c++ -DHAVE_MOVE_AND_COPY -DTRY_DEDUCTION -std=c++17 example.cc && ./a.out 
VocalClass()
VocalClass(VocalClass &&)
VocalClass()
VocalClass(VocalClass &&)

没有HAVE_MOVE_AND_COPY, 模板类型推导中断:

sky@sunrise:~$ c++ -DTRY_DEDUCTION -std=c++17 example.cc && ./a.out 
example.cc: In function ‘int main()’:
example.cc:27:31: error: class template argument deduction failed:
     Wrapper w2 { VocalClass() };
                               ^
example.cc:27:31: error: no matching function for call to ‘Wrapper(VocalClass)’
example.cc:12:5: note: candidate: ‘template<class T> Wrapper(Wrapper<T>&&)-> Wrapper<T>’
     Wrapper(Wrapper &&) = default;
     ^~~~~~~
example.cc:12:5: note:   template argument deduction/substitution failed:
example.cc:27:31: note:   ‘VocalClass’ is not derived from ‘Wrapper<T>’
     Wrapper w2 { VocalClass() };
                               ^
example.cc:11:5: note: candidate: ‘template<class T> Wrapper(const Wrapper<T>&)-> Wrapper<T>’
     Wrapper(Wrapper const &) = default;
     ^~~~~~~
example.cc:11:5: note:   template argument deduction/substitution failed:
example.cc:27:31: note:   ‘VocalClass’ is not derived from ‘const Wrapper<T>’
     Wrapper w2 { VocalClass() };
                               ^
example.cc:5:8: note: candidate: ‘template<class T> Wrapper(Wrapper<T>)-> Wrapper<T>’
 struct Wrapper {
        ^~~~~~~
example.cc:5:8: note:   template argument deduction/substitution failed:
example.cc:27:31: note:   ‘VocalClass’ is not derived from ‘Wrapper<T>’
     Wrapper w2 { VocalClass() };
                  

问题

有什么方法可以同时进行模板类型推导和聚合初始化?

4

2 回答 2

5

首先,术语是“类模板参数推导”。

其次,你需要的是一个扣减指南

template<class T> 
struct Wrapper {
    T value;
};

template <typename T>
Wrapper(T) -> Wrapper<T>; // this is a deduction guide

如果没有构造函数,则需要其他方式来指导推导。这就是它的用途,它允许:

Wrapper w{4}; // ok, Wrapper<int>
于 2018-11-04T03:18:00.457 回答
1

您对“聚合”一词的含义有误解。

首先,您正在做的事情称为列表初始化。如果实例的类型是聚合,则列表初始化只会聚合初始化您的实例。聚合初始化允许您使用初始化列表按顺序初始化基类和/或类的成员。

cppreference

聚合是以下类型之一:

  • 数组类型
  • 类类型(通常是结构或联合),具有
    • 没有用户提供的、继承的或显式的构造函数(允许显式默认或删除的构造函数)
  • 没有虚拟、私有或受保护 (C++17 起) 基类
  • 没有虚拟成员函数
  • 没有默认的成员初始化器

第二个要点在这里适用。由于您在定义时有一个用户提供的构造函数(由用户编写而不是由编译器生成的构造函数)HAVE_MOVE_AND_COPY,因此您的类型不是聚合,编译器只会寻找构造函数来初始化您的实例。

Barry 涵盖了有关如何使用类模板参数推导进行聚合的其余部分。

于 2018-11-04T03:34:20.500 回答