在 C++ 的模板函数中使用适当的字符串字面量。

4
我有一个函数,它是一个模板函数,可以匹配每个std::basic_string实例:
template <typename _valueType>
void addFoo(std::basic_string<_valueType>& string_)
{
    string_ += "foo";
}

我希望"foo"可以根据_valueType的不同而有所变化。我不想使用特化,因为在实际项目中我有整个类模板需要处理,这将是很多工作。
目前我在函数体中使用if constexpr,但当我有带有默认参数的函数时,问题就出现了,像这样:

template <typename _valueType>
void foo(_valueType const delimiter_ = '.');

2
可能存在无限数量的basic_string实例。您还缺少一个basic_string的模板参数--它需要2个而不是1个类型(第二个是特性类):我可以编写一个basic_string<double,SomeTraits>。此外,您的一个案例使用了单引号'.',另一个案例使用了双引号字符串"foo"。请更具体地说明您的实际问题是什么? - Yakk - Adam Nevraumont
我决定仅支持默认特征,但如果有强有力的反对意见,请告知我。我的实际问题在于:根据字符串实例化的方式,我想使用适当的字符串/字符字面量。 - Poeta Kodu
由于某些原因,您忽略了我的第一句话。我会让它更长一点:basic_string<char16_t>basic_string<char>basic_string<unsigned char>basic_string<char32_t>basic_string<wchar_t> 都是可能的 basic_string 类型。我不知道 _valueType 可以变化到什么程度;而我认为您应该知道。如果它可以随意变化,那么就有不同的好答案,而如果(比如说)您只支持两种情况,就会有不同的好答案。 - Yakk - Adam Nevraumont
我将使用SFINAE来限制它只支持你提到的那些类型,因为我希望它能够支持所有这些。谢谢。 - Poeta Kodu
2个回答

2

使用外部类进行特化:

template <class>
struct basic_string_literals;

template <>
struct basic_string_literals<char> {
    constexpr char * foo = "foo";
    constexpr char delim = '.'
};

template <typename _valueType>
void addFoo(std::basic_string<_valueType>& string_)
{
    string_ += basic_string_literals<_valueType>::foo;
}

template <typename _valueType>
void foo(_valueType const delimiter_ = basic_string_literals<_valueType>::delim);

这样,您就不必针对每个以_valueType为模板的函数/类进行专门化,只需进行单一点的专门化。

这是一个我没有想到的好方法。不幸的是,它强制我将每个字面值都放在 basic_string_literals 特化中。然而,如果没有其他选择,我会使用你的方法。 - Poeta Kodu

2
我假设你只支持 charwchar_t。 如果你想支持更长的枚举类型列表,也可以这样做。 这不适用于非本地枚举类型列表。
template<class Index, class...Args>
decltype(auto) dispatch( Index, Args&&... args ) {
  return std::get<Index{}>( std::forward_as_tuple( std::forward<Args>(args)... ) );
}

这是一个方便的小助手,可以让您在编译时分派任意数量的参数。
现在,在您的类中定义以下内容:
template<class Char, class WChar>
static decltype(auto) pick(Char&& c, WChar&& w) {
  using is_wchar_t = std::is_same<_valueType, wchar_t>;
  return dispatch( is_wchar_t{}, std::forward<Char>(c), std::forward<WChar>(w) );
}

现在您可以这样做:
template <typename _valueType>
void addFoo(std::basic_string<_valueType>& string_)
{
  string_ += pick( "foo", L"foo" );
}

如果你不喜欢DRY原则的失败

#define BOTH_CHARTYPE(...) __VA_ARGS__, L __VA_ARGS__

template <typename _valueType>
void addFoo(std::basic_string<_valueType>& string_)
{
  string_ += pick( BOTH_CHARTYPE("foo") );
}

或者进行一些宏魔法,使L正确附加到""上。

这是用编写的,但可以相对容易地适应;将Index{}替换为Index::value,并用->decltype()子句替换decltype(auto)

template<class...Ts>
struct types { using type=types; };
template<std::size_t I>
using index_t=std::integral_constant<std::size_t, I>;

template<class T, class Types>
struct type_index;
template<class T, class...Ts>
struct type_index<T, types<T, Ts...>>:index_t<0> {};
template<class T, class T0, class...Ts>
struct type_index<T, types<T0, Ts...>>:index_t<
  type_index<T, types<Ts...>>{}+1
>{};

using char_types = types<char, wchar_t, char16_t, char32_t>;

template<class T>
using char_index = type_index<T, char_types >;

#define ALL_CHAR_TYPES(...) __VA_ARGS__, L __VA_ARGS__, u __VA_ARGS__, U __VA_ARGS__

template<class T, class...Chars>
decltype(auto) pick(Chars&&...chars) {
  return dispatch( char_index< T >{}, std::forward<Chars>(chars) );
}

void addFoo( std::basic_string<_valueType>& string_ ) {
  string_ += pick<_valueType>( ALL_CHAR_TYPES("foo") );
}

谢谢,这可能是最好的答案。我会尝试扩展它以支持char16_t和char32_t。 我现在正在使用C++17 :) - Poeta Kodu
1
@razzorflame 使用 std::integral_constant<std::size_t, X>,其中 X 是基于类型的 03,而不是使用 std::is_same - Yakk - Adam Nevraumont
我不太清楚如何指定哪个积分与哪种类型相关联。 - Poeta Kodu
1
@razzorflame 添加了4种类型分派的草图。可能会有拼写错误。根据编译器,ALL_CHAR_TYPES可能需要额外的宏来正确附加L"foo" - Yakk - Adam Nevraumont
char_index缺少using,而index_t在模板中可能有拼写错误。感谢您的解决方案! - Poeta Kodu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接