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

C++可变参数模板函数参数包推导失败及简化调用需求

问题描述

给定以下代码:

template <class P>
concept Pair = requires(P pair)
{
    typename P::first_type;
    typename P::second_type;
    pair.first;
    pair.second;
    { pair.first } -> std::same_as<typename P::first_type &>;
    { pair.second } -> std::same_as<typename P::second_type &>;
};

template <typename T>
setTag(const std::string_view key, const T &value)
{
    foo(key.data(), value);
    // things in here
}

template <Pair... Pairs>
void setTags(Pairs... pairs)
{
    ((setTag(std::get<0>(pairs), std::get<1>(pairs)), ...));
}

当前调用setTags时,必须为每个参数显式构造pair:

setTags(std::make_pair("key1", 1),
        std::make_pair("key2", "example"),
        std::make_pair("key3", std::set{1, 2}));

我希望能无需显式调用make_pair,直接通过如下方式调用:

setTags({"key1", 1},
        {"key2", "example"},
        {"key3", std::set{1, 2}});

但这会触发编译错误:

note: candidate template ignored: substitution failure: deduced incomplete pack <(no value), (no value), (no value)> for template parameter 'Pairs'

原因是编译器无法推导参数包中参数的类型,我引入了C++20 Concept来约束类型,但并未解决问题。

尝试过的方案

  • 使用nlohmann_json等JSON库接收参数,但会引入不必要的依赖。
  • 使用std::initializer_list,示例如下:
    template <typename T>
    void setTags(std::initializer_list<std::pair<std::string,T>> pairs)
    {
        for(const auto &[key, value] : pairs){
            setTag(key, value);
        }
    }
    
    但该方案会丢失泛型性,要求所有值的类型必须相同:
    setTags<int>({{"key1", 1}, {"key2",2}});
    

编辑补充

根据建议,我编写了一个验证std::pair、std::get且first为字符串类型的Concept:

template <typename P>
concept KeyValuePair = requires(P pair) {
    typename std::decay_t<P>::first_type;
    typename std::decay_t<P>::second_type;

    { std::get<0>(pair) } -> std::convertible_to<typename std::decay_t<P>::first_type>;
    { std::get<1>(pair) } -> std::convertible_to<typename std::decay_t<P>::second_type>;

    std::is_convertible_v<typename std::decay_t<P>::first_type, std::string>;
};

以及使用CTAD的简化调用方式:

setTags(std::pair("key1", 1),
        std::pair("key2", "example"),
        std::pair("key3", std::set{1, 2}));

但仍希望实现无需显式编写std::pair的调用方式。


解决方案

要支持直接用初始化列表{key, value}作为参数传递给setTags,核心是让编译器能把每个初始化列表推导为合适的std::pair类型,以下是两种简洁可行的实现方式:

方法1:利用std::pair的CTAD特性重载参数包

直接将模板参数包约束为std::pair的实例,编译器会自动通过CTAD推导每个初始化列表对应的std::pair类型:

template <typename... K, typename... V>
void setTags(std::pair<K, V>... pairs)
{
    ((setTag(std::get<0>(pairs), std::get<1>(pairs)), ...));
}

这种方式最直接,不需要修改原有Concept或复杂逻辑,完全适配你的期望调用方式。

方法2:优化Concept适配初始化列表推导

如果想保留Concept约束,需要调整Concept使其兼容临时pair对象和初始化列表推导,同时让参数包支持转发引用:

template <typename P>
concept KeyValuePair = requires(P pair) {
    // 放宽约束,只要能通过std::get获取可转换为字符串的键即可
    { std::get<0>(pair) } -> std::convertible_to<std::string_view>;
};

template <KeyValuePair... Pairs>
void setTags(Pairs&&... pairs)
{
    ((setTag(
        std::get<0>(std::forward<Pairs>(pairs)),
        std::get<1>(std::forward<Pairs>(pairs))
    ), ...));
}

此时编译器会自动将每个{key, value}推导为临时std::pair对象,并通过Concept校验。

验证调用

使用上述任意一种方法后,都可以直接按期望方式调用:

setTags({"key1", 1},
        {"key2", "example"},
        {"key3", std::set{1, 2}});

编译器会自动推导每个初始化列表对应的std::pair类型,既保留泛型性,又不需要显式构造pair对象。


内容的提问来源于stack exchange,提问作者asm4ever

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 18:44:54