You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 19:20:50