当我在模板参数的顺序上有冲突的需求时,该怎么办?
我正在编写以下函数:
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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。