当我在模板参数的顺序上有冲突的需求时,该怎么办?

编程语言 2026-07-09

我正在编写以下函数:

template <typename T, typename Generator>
my_container_t<T> generate(size_t size, Generator generator_by_index) 
noexcept(noexcept(::std::declval<Generator>()(0)));

先别管实现,也别管my_container_t是什么。现在,我有两个互相矛盾的期望:

  • 有时,生成器返回的类型与我希望用于容器的类型不同,例如它返回int,而我想要char。因此我想通过下面的方式强制指定:

auto container = generate<char>(size, get_some_int); * 有时,我宁愿不指定容器类型,而是让它通过检测器(Detector)自动检测,即让T变为decltype(::std::declval<Generator>()(0))

当前模板参数的排列使我能够实现第一个期望:从一开始就指定模板参数到某个点,其余部分自动推导。但是编译器并不允许我写出:

auto container = generate(size, my_generator_of_ints);

因为它“不能”推导出T

我当然也可以修改声明:

template <typename Generator>
my_container_t<decltype(::std::declval<Generator>()(0))>
generate(size_t size, Generator generator_by_index) 
noexcept(noexcept(::std::declval<Generator>()(0)));

或:

template <typename Generator, typename T = ::std::declval<Generator>()(0)>
my_container_t<T> generate(size_t size, Generator generator_by_index) 
noexcept(noexcept(::std::declval<Generator>()(0)))

但如果这样做,我就会失去指定所需容器元素类型的能力。我可以把T作为第二个参数添加,但那样我就必须显式指定Generator的类型,这点我宁愿避免。

我能否安排成两种表述都被接受且类型能够正确推导?

解决方案

使用两个重载的另一种答案中的解决方案工作得非常好,尽管有一点点麻烦——需要复制签名然后再转发所有参数。因此,这里给出一种基于 @Igor Tandetnik的注释的占位类型的替代方案。请参阅 GodBolt演示

struct deduce {
    template<typename Supplied, typename Deduced>
    using type = std::conditional_t<std::is_same_v<Supplied, deduce>, Deduced, Supplied>;
};

template <typename Supplied = deduce, typename Generator, 
          typename T = deduce::type<Supplied, decltype(::std::declval<Generator>()(0))>>
my_container_t<T> generate(size_t size, Generator generator_by_index) 
noexcept(noexcept(::std::declval<Generator>()(0)))

我认为使用一个有意义名称的专用占位类型来表示占位符,实际上会让这段代码更易读。

与两种重载相比,使用这种方法有一个缺点:推导出的类型即使未被使用也必须有效,因为它被作为模板参数传递给conditional_t。 (重载解决方案会通过SFINAE直接丢弃可推导的重载)

备选方案

正如 @cpplearner所暗示的,你对函数的原始表述,以及你提出的第一种替代形式,可以在同一个翻译单元中共存;编译器显然不会对实例化哪一个感到困惑!这就是SFINAE的魔力……

#include <vector>
#include <type_traits>
#include <algorithm>
#include <iostream>

template <typename T>
using my_container_t = std::vector<T>;

template <typename T, typename Generator>
my_container_t<T> generate(size_t size, Generator generator_by_index) 
noexcept(noexcept(::std::declval<Generator>()(0)))
{
    my_container_t<T> result;
    for (size_t i = 0; i < size; i++) {
        result.emplace_back(generator_by_index(i));
    }
    return result;
}

template <typename Generator>
my_container_t<decltype(::std::declval<Generator>()(0))>
generate(size_t size, Generator generator_by_index) 
noexcept(noexcept(::std::declval<Generator>()(0)))
{
    using T = decltype(::std::declval<Generator>()(0));
    return generate<T, Generator>(size, std::forward<Generator>(generator_by_index));
}


int main() {
    auto vec1 = generate(5, [](auto i) { return (i+1) * 10;});
    std::cout << "vec1: " << vec1[0] << " .. " << vec1[4] << '\n';
    auto vec2 = generate<float>(5, [](auto i) { return (i+1) * 0.1;});
    std::cout << "vec2: " << vec2[0] << " .. " << vec2[4] << '\n';
}

可以在 GodBolt 上看到它工作。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章