C++ で文字列を連結をする関数テンプレートの記事を読みました。 char*
と wchar_t*
を統一的に扱うという試みです。
この試みを更に一般化して文字列的な型について全般に対応しようとするとどのように書けるだろうかということを考えてみました。 そこで思い出したのは C++17 以降は文字列的なものは std::basic_string_view
で受けとるのが基本ということです。 逆に言えば std::basic_string_view
で受け取ることが可能なものは文字列的なものとみなすのは妥当な考えでしょう。
そして一旦 std::basic_string_view
になったならば統一的なインターフェイスで扱えるので実装は簡単です。 ただ、 C++ の推論規則では直接的に仮引数を推論させることが出来ないので一旦は元の型で受け取る必要があります。 また、配列の先頭要素を指すポインタという形で表現された文字列は型システム上の例外として場合分けが必要なので少し回りくどい表現になります。
以上のような思考過程を経て以下のようなプログラムを書きました。 C++17 以降を想定しています。
#include <algorithm>
#include <iostream>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
template <class T, class = void>
class is_string : public std::false_type {};
template <class T>
struct is_string<T, std::void_t<decltype(std::basic_string_view<typename T::value_type>(std::declval<T>()))>> : public std::true_type {};
template <class T>
struct is_string<T *, std::void_t<decltype(std::basic_string_view<T>(std::declval<T *>()))>> : public std::true_type {};
template <class T>
inline constexpr bool is_string_v = is_string<std::decay_t<T>>::value;
template <class T, class U = void>
struct string_element {
static_assert(!sizeof(T), "Expected string-like type.");
};
template <class T>
struct string_element<T, std::void_t<decltype(std::basic_string_view<typename T::value_type>(std::declval<T>()))>> {
using type = typename std::basic_string_view<typename T::value_type>::value_type;
};
template <class T>
struct string_element<T *, std::void_t<decltype(std::basic_string_view<T>(std::declval<T *>()))>> {
using type = typename std::basic_string_view<T>::value_type;
};
template <class T>
using string_element_t = typename string_element<std::decay_t<T>>::type;
template <class T, class... U>
inline constexpr bool is_all_same_v = (std::is_same_v<T, U> && ...);
template <class T, class... U>
auto string_append(const T &head, const U &...tail)
-> std::enable_if_t<is_all_same_v<std::decay_t<T>, std::decay_t<U>...> && is_string_v<T>, std::basic_string<string_element_t<T>>> {
using char_type = string_element_t<T>;
auto input_strings = std::initializer_list<const std::basic_string_view<char_type>>{head, tail...};
std::basic_string<char_type> result;
for (auto &x : input_strings) std::copy(std::begin(x), std::end(x), std::back_inserter(result));
return result;
}
int main(void) {
// char* 同士の連結
const std::string foo = string_append("abc", "defg", "hi");
std::cout << foo << std::endl;
// wchar_t* 同士の連結
const std::wstring bar = string_append(L"1", L"2345", L"678");
std::wcout << bar << std::endl;
// std::string 同士の連結
const std::string baz = string_append(foo, foo);
std::cout << baz << std::endl;
// std::wstring 同士の連結
const std::wstring qux = string_append(bar, bar);
std::wcout << qux << std::endl;
// その他の型の文字列の連結
const std::u16string quux = string_append(u"あ", u"いう", u"えお");
const std::u32string corge = string_append(U"いろ", U"はにほ", U"へと");
}
念のために強調しておきますが、これは文字列的なもの全般に対応するにはという前提を付けたときにはこう書くことも出来るという一例であって、不必要に一般化することを是とするものではありません。