C++20中如何让编译器推导含初始化列表的concept auto...函数参数
问题:C++20 Concepts下支持隐式转换的可变参数函数问题
我正在学习C20 concepts,希望定义一个可接受任意数量Foo实例的函数,让编译器自动将输入参数隐式转换为Foo实例。但使用initializer_list时,隐式转换似乎无法生效,代码最后一行就是问题所在。请问这在C20和concepts下是否可行?如果可行,应如何修改代码?如果不可行,有没有其他方法让编译器自动完成隐式转换?
原代码
#include <iostream> #include <concepts> struct Foo { Foo(){ std::cout << "No argument!\n"; } Foo(int){ std::cout << "One argument!\n"; } Foo(int, int){ std::cout << "Two arguments!\n"; } }; // function takes a single Foo void foo(Foo foo){} // function takes any number of Foos template <typename... Foos> requires (std::is_convertible_v<Foos, Foo> && ...) void foos(Foos... foos){} int main() { // converting ctors work Foo x = 42; Foo y = {42, 42}; Foo z = {}; std::cout << "\n"; // argument deduction works for foo foo(42); foo({42, 42}); foo({}); std::cout << "\n"; // argument deduction works for foos using only second ctor foos(1, 2, 3); std::cout << "\n"; // argument deduction does not work for foos if we use initializer lists foos(42, {42, 42}, {}); }
编译器报错
GCC
error: too many arguments to function 'void foos(Foos ...) [with Foos = {}]'
MSVC
'initializer list': is not a valid template argument for 'Foos'
Clang
candidate template ignored: substitution failure: deduced incomplete pack <int, (no value), (no value)> for template parameter 'Foos'
问题原因
核心问题在于大括号初始化列表(braced-init-list)没有类型,无法参与模板参数推导。在单个参数的foo(Foo)函数中,目标类型明确为Foo,编译器会直接用大括号初始化列表构造Foo对象;但在可变参数模板foos(Foos...)中,编译器需要推导每个Foos参数的类型,而大括号初始化列表无法提供类型信息,导致推导失败。
解决方案
方案一:使用非推导上下文明确目标类型
通过非推导上下文告诉编译器,每个参数的目标类型是Foo,这样编译器会跳过类型推导,直接将参数(包括大括号初始化列表)转换为Foo。可以用C++20引入的std::type_identity实现:
#include <iostream> #include <concepts> #include <type_traits> struct Foo { Foo(){ std::cout << "No argument!\n"; } Foo(int){ std::cout << "One argument!\n"; } Foo(int, int){ std::cout << "Two arguments!\n"; } }; void foo(Foo foo){} // 使用std::type_identity_t<Foo>创建非推导上下文 template <typename... Args> requires (std::convertible_to<Args, Foo> && ...) void foos(std::type_identity_t<Foo>... foos){} int main() { Foo x = 42; Foo y = {42, 42}; Foo z = {}; std::cout << "\n"; foo(42); foo({42, 42}); foo({}); std::cout << "\n"; foos(1, 2, 3); std::cout << "\n"; // 现在可以正常编译,编译器会自动转换每个参数为Foo foos(42, {42, 42}, {}); }
方案二:改用initializer_list参数
如果可以接受调用方式改为传递单个初始化列表,直接用std::initializer_list<Foo>作为参数,编译器会自动将列表中的每个元素转换为Foo:
#include <iostream> #include <concepts> #include <initializer_list> struct Foo { Foo(){ std::cout << "No argument!\n"; } Foo(int){ std::cout << "One argument!\n"; } Foo(int, int){ std::cout << "Two arguments!\n"; } }; void foo(Foo foo){} // 接受任意数量可转换为Foo的元素 void foos(std::initializer_list<Foo> foos){} int main() { // ... 其他代码不变 // 调用方式改为传递初始化列表 foos({42, {42, 42}, {}}); }
说明
方案一保留了原有的多参数调用方式,完全符合需求;方案二简化了函数实现,但需要调整调用语法。两种方案都能让编译器自动完成隐式转换,包括处理大括号初始化列表的情况。
内容的提问来源于stack exchange,提问作者joergbrech
相关产品推荐
相关产品推荐

