如何正确实现参数完美转发?求GenerateDictionary模板函数优化方案
C++模板函数GenerateDictionary的完美转发优化与问题分析
我尝试实现通用的GenerateDictionary(...)模板函数,让它能同时接受左值和右值类型的generator,当前代码能运行,但不确定完美转发的写法是否正确,希望得到优化建议。我知道这里用完美转发可能没实际意义,但还是想搞懂该场景下安全使用完美转发的方式。另外我还尝试限制函数仅接受std::mt19937类型,以下是两个版本的实现代码:
版本一
std::string GenerateWord(std::mt19937& generator, int max_length) { const int length = std::uniform_int_distribution(1, max_length)(generator); std::string word; word.reserve(length); for (int i = 0; i < length; ++i) { word.push_back( std::uniform_int_distribution<int>('a', 'z')(generator)); } return word; } template <template <typename> typename Container, typename Rand> Container<std::string> GenerateDictionary(Rand&& generator, int word_count, int max_length) { std::vector<std::string> words; words.reserve(word_count); for (int i = 0; i < word_count; ++i) { words.push_back( GenerateWord(std::forward<Rand&>(generator), max_length)); } return Container(words.begin(), words.end()); }
版本二
template <typename T> class CanReserve { struct Small { char c; }; struct Large { char arr[2]; }; template <typename C> static Small test(decltype(&C::reserve)); template <typename C> static Large test(...); public: enum { value = sizeof(test<T>()) == sizeof(Small) }; }; template <typename Rand, typename = std::enable_if<std::is_same_v<Rand, std::mt19937>>> std::string GenerateWord(Rand&& generator, int max_length) { const int length = std::uniform_int_distribution(1, max_length)(generator); std::string word; word.reserve(length); auto letter_distr = std::uniform_int_distribution<int>('a', 'z'); for (int i = 0; i < length; ++i) { word.push_back(letter_distr(generator)); } return word; } template <template <typename...> typename Container, typename Rand, typename = std::enable_if<std::is_same_v<Rand, std::mt19937>>> Container<std::string> GenerateDictionary(Rand&& generator, int word_count, int max_length) { Container<std::string> words; if constexpr (CanReserve<Container<std::string>>::value) { words.reserve(word_count); } for (int i = 0; i < word_count; ++i) { words.push_back( GenerateWord(std::forward<Rand>(generator), max_length)); } return words; }
问题分析与优化建议
关于完美转发的正确性
版本一的问题:
GenerateWord仅接受左值引用std::mt19937&,所以GenerateDictionary里的std::forward<Rand&>(generator)实际上总是转发成左值引用,完美转发没有发挥作用——即使传入右值std::mt19937,也会被转成左值,无法触发右值重载(这里也没有右值重载)。- 先创建
std::vector再转换为目标容器,会产生额外的元素拷贝,效率不如直接操作目标容器。 - 容器模板参数
template <typename> typename Container限制过死,无法适配带默认模板参数的容器(比如std::set<std::string>实际是std::set<std::string, std::less<std::string>, std::allocator<std::string>>),应该改成template <typename...> typename Container来支持任意模板参数的容器。
版本二的进步与不足:
GenerateWord用了Rand&&配合std::forward<Rand>,完美转发的写法是正确的:传入左值时Rand推导为左值引用,forward转发左值;传入右值时推导为值类型,forward转发右值。- 但类型限制
std::is_same_v<Rand, std::mt19937>有bug:如果传入左值std::mt19937&,Rand会被推导为std::mt19937&,此时std::is_same_v返回false,导致编译失败。正确的做法是先移除引用再判断:std::is_same_v<std::remove_reference_t<Rand>, std::mt19937>。
类型限制的正确写法
把版本二中的std::enable_if条件改成:
typename = std::enable_if<std::is_same_v<std::remove_reference_t<Rand>, std::mt19937>>
这样无论传入左值还是右值,都能正确识别是否为std::mt19937类型。
CanReserve的简化实现
C++17之后可以用更简洁的SFINAE方式判断容器是否支持reserve:
template <typename T> concept CanReserve = requires(T t) { t.reserve(std::size_t{}); };
如果用C++11/14,可以改成:
template <typename T, typename = void> struct CanReserve : std::false_type {}; template <typename T> struct CanReserve<T, decltype(std::declval<T>().reserve(std::size_t{}), void())> : std::true_type {};
这样比原来的sizeof技巧更直观。
最终优化后的核心代码示例
#include <random> #include <string> #include <vector> #include <type_traits> template <typename T> struct CanReserve : std::false_type {}; template <typename T> struct CanReserve<T, decltype(std::declval<T>().reserve(std::size_t{}), void())> : std::true_type {}; template <typename Rand, typename = std::enable_if<std::is_same_v<std::remove_reference_t<Rand>, std::mt19937>>> std::string GenerateWord(Rand&& generator, int max_length) { const int length = std::uniform_int_distribution(1, max_length)(generator); std::string word; word.reserve(length); static const auto letter_distr = std::uniform_int_distribution<int>('a', 'z'); for (int i = 0; i < length; ++i) { word.push_back(letter_distr(generator)); } return word; } template <template <typename...> typename Container, typename Rand, typename = std::enable_if<std::is_same_v<std::remove_reference_t<Rand>, std::mt19937>>> Container<std::string> GenerateDictionary(Rand&& generator, int word_count, int max_length) { Container<std::string> words; if constexpr (CanReserve<Container<std::string>>::value) { words.reserve(word_count); } for (int i = 0; i < word_count; ++i) { words.push_back(GenerateWord(std::forward<Rand>(generator), max_length)); } return words; }
内容的提问来源于stack exchange,提问作者Viktor Kustov
相关产品推荐
相关产品推荐

