3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

VS2015環境でconstexprな文字列操作

Last updated at Posted at 2019-04-12

元々はhttps://github.com/Neargye/nameof を見て使ってみたいと思ったのですが, VS2015環境ではstring_viewが使えない.
その為, 代わりになるようなクラスを自作することにしました.

文字列を操作するためにはバッファを用意する必要がありますが, これをコンパイル時に用意する為には可変長文字列ではなく固定長文字列になります.

// 最大 N-1 文字のテキストクラス
template<int N>
class fixed_text
{
private:
  char buffer_[N] = {0};
}

次にバッファの初期化ですが, 以下の様にfor文で書こうとすると失敗します.

理由は, VS2015ではconstexpr関数の中でfor文が使えない為です(VS2017以降だと使えます).

// 最大 N-1 文字のテキストクラス
template<int N>
class fixed_text
{
public:
  constexpr fixed_text(const char* text)
  {
  // これはエラーになる
    for(auto i=0; *text != '\0' && i < ()N - 1); ++i)
      buffer_[i] = *(text + i);
    buffer_[N-1] = '\0'
  }
private:
  char buffer_[N] = {0};
}

int main()
{
  // sizeof("Hoge")は必要な配列数(5)が返る
  constexpr auto text = fixed_text<sizeof("Hoge")>("Hoge");
}
 

その為, 以下の様に再帰を使ってコピーを行います

textの先頭をバッファにコピーし, textの次の位置をコンストラクタに再帰的に渡して文字列のコピーを行う(テンプレート関数なので正確には再帰ではない)

その際, 末尾とそれ以外で処理を分けるために, 引数にダミークラスのポインタを渡すようにする

// オーバーロードの為のダミークラス
namespace _tmp
{
    template<int N> struct Dummy;
}

// 内部のコピー処理のためのコンストラクタ
template <int M>
constexpr fixed_text(const char *text, _tmp::Dummy<M> *)
: fixed_text(*text == '\0' ? text : text + 1, (_tmp::Dummy<M - 1> *)nullptr) // 次の文字のコピーを再帰的に呼び出す
{
   buffer_[N - 1 - M] = *text;
}
  
// バッファの最後まで到達したら0を入れて終わる
constexpr fixed_text(const char *text, _tmp::Dummy<0> *)
{
  buffer_[N - 1] = '\0';
}

// 失敗例
// 素直な以下のやり方だと末尾到達時に処理を終わらすことができない
constexpr fixed_text(const char*text, int index)
: fixed_text(*text == '\0' ? text : text + 1)  // <--  ここでindexによって再帰するかどうか分岐することができない
{}

まとめると以下の様になります

// オーバーロードの為のダミークラス
namespace _tmp
{
	template<int N>	struct Dummy;
}

// 最大 N-1 文字のテキストクラス
template<int N>
class fixed_text
{
public:
  // 外部公開用のコンストラクタ
  constexpr fixed_text(const char *text = "")
  : fixed_text(text, (_tmp::Dummy<N - 1> *)nullptr) {}
private:
  // 内部のコピー処理のためのコンストラクタ
  template <int M>
  constexpr fixed_text(const char *text, _tmp::Dummy<M> *)
  : fixed_text(*text == '\0' ? text : text + 1, (_tmp::Dummy<M - 1> *)nullptr)
  {
    buffer_[N - 1 - M] = *text;
  }
  
  // バッファの最後まで到達したら0を入れて終わる
  constexpr fixed_text(const char *text, _tmp::Dummy<0> *)
  {
    buffer_[N - 1] = '\0';
  }

private:
  char buffer_[N] = {0};
}

int main()
{
  constexpr auto text = fixed_text<sizeof("Hoge")>("Hoge");
}

また, マクロを書くことでテンプレート引数とコンストラクタの引数で2重に同じものを書く必要がなくなります.

#define FIXED_TEXT(Arg) foxed_text<sizeof(Arg)>(Arg)

int main()
{
  constexpr auto text = FIXED_TEXT("Hoge");
}

最終的には以下の様になりました。
constexprにするために文字列操作に関しては破壊的に行うのではなく、操作した文字列を新たに生成して返すというやり方になります.
いつか詳しく説明するかもしれません.

fixed_text.h

namespace ft
{
	namespace _tmp
	{
		template<int N>
		struct Dummy;
	}

	namespace util
	{
		// 文字列のサイズを取得する(自作版)
		inline constexpr int size_of(const char* text)
		{
			// sizeofは引数で渡された場合には挙動が変わるっぽいので
			// 地道に数える
			return *text == '\0' ? 0 : (1 + size_of(text + 1));
		}

		// textが末尾でなければポインタを1勧める
		inline constexpr auto increment(const char* text)
		{
			return *text == '\0' ? text : text + 1;
		}

		inline constexpr bool equal(const char* text1, const char* text2)
		{
			return (*text1 == *text2) && (*text1 == '\0' ? true : equal(text1 + 1, text2 + 1));
		}

		template<class T>
		inline constexpr T min(const T& a, const T& b)
		{
			return a < b ? a : b;
		}

		template<class T>
		inline constexpr T max(const T& a, const T& b)
		{
			return a > b ? a : b;
		}

		template<class T>
		inline constexpr T clamp(const T& v, const T& min_v, const T& max_v)
		{
			return min(max_v, max(min_v, v));
		}
	}

	template <int N>
	class fixed_text
	{
		static_assert(N > 0, "N must be positive number");
	  public:
	  	// コンストラクタ
		constexpr fixed_text(const char *text = "")
			: fixed_text(text, (_tmp::Dummy<N - 1> *)nullptr)
		{
		}

		// 別のfixed_textからコピー
		template <int M>
		constexpr fixed_text(const fixed_text<M> &other)
			: fixed_text(other.c_str(), M)
		{	}

		// text1, text2を連結する
		constexpr fixed_text(const char *text1, const char *text2)
		: fixed_text(text1, text2, (_tmp::Dummy<N - 1> *)nullptr)
		{	}

		// バッファサイズを変更する
		template<int M>
		constexpr fixed_text<M> resize() const
		{
			return fixed_text<M>(c_str());
		}

		// 末尾n文字を削除
		constexpr fixed_text<N> remove_suffix(int n) const
		{
			return n <= 0 ? *this : fixed_text<N>(c_str(), size() - n );
		}

		// 先頭n文字を削除
		constexpr fixed_text<N> remove_prefix(int n) const
		{
			return n <= 0 ? *this : fixed_text<N>( n >= N ? "" : buffer_ + n);
		}

		// 先頭がtextだった場合, その文字列を削除して返す
		constexpr fixed_text<N> remove_prefix(const char* text) const
		{
			return is_match(text) ? remove_prefix(util::size_of(text)) : *this;
		}

		// 任意の関数を実行する
		template<class FUNC>
		constexpr auto apply(FUNC&& func) const
		{
			return func(*this);
		}

		// この文字列の先頭がtextと一致するか調べる
		constexpr bool is_match(const char* text) const
		{
			return *text != '\0' && is_match(text, 0);
		}

		// textを含むかどうかを返す
		constexpr bool is_match_any(const char* text) const
		{
			return *text != '\0' && is_match_any(text, 0);
		}

		// 先頭から文字cを検索. 見つかった位置のインデックスを返す.
		// 見つからない場合は-1を返す
		constexpr int find_index(char c) const
		{
			return find_index(c, 0);
		}

		// 末尾から文字cを検索. 見つかった位置のインデックスを返す.
		// 見つからない場合は-1を返す
		constexpr int rfind_index(char c) const
		{
			return rfind_index(c, size() - 1);
		}

		// 引数の文字列を後ろに連結した文字列を返す
		template<int M>
		constexpr fixed_text<N+M> append(const fixed_text<M>& other) const
		{
			return fixed_text<N+M>(c_str(), other.c_str());
		}

		// const char*を返す
		constexpr char const *c_str() const
		{
			return buffer_;
		}

		// 文字数を返す
		constexpr size_t size() const
		{
			return size_of(c_str());
		}

		// バッファサイズを返す
		constexpr size_t max_size() const
		{
			return N;
		}

		// 空文字かどうかを返す
		constexpr bool empty() const
		{
			return size() == 0;
		}

		// 先頭の文字を返す
		constexpr char front() const
		{
			return size() == 0 ? '\0' : buffer_[0];
		}

		// 末尾の文字を返す
		constexpr char back() const
		{
			return size() == 0 ? '\0' : buffer_[size()-1];
		}

		// インデックスアクセス(getterのみ)
		constexpr char operator[](int index) const
		{
			return buffer_[index];
		}

		constexpr bool operator!=(const char* text) const
		{
			return !(operator==(text));
		}

		template<int M>
		constexpr bool operator!=(const fixed_text<M>& other) const
		{
			return !(operator==(text));
		}

		constexpr bool operator==(const char* text) const
		{
			return util::equal(c_str(), text);
		}

		template<int M>
		constexpr bool operator==(const fixed_text<M>& other) const
		{
			return util::equal(c_str(), other.c_str());
		}

		// 以下は再帰用 ---------------------------------
	  private:
		template <int M>
		constexpr fixed_text(const char *text, _tmp::Dummy<M> *)
			: fixed_text( *text == '\0' ? text : text + 1, (_tmp::Dummy<M - 1> *)nullptr)
		{
			buffer_[N - 1 - M] = *text;
		}

		constexpr fixed_text(const char *text, _tmp::Dummy<0> *)
		{
			buffer_[N - 1] = '\0';
		}
		
		template<int M>
		constexpr fixed_text(const char* text1, const char* text2, _tmp::Dummy<M>*)
		:fixed_text( (*text1 == '\0' ? text1 : text1 + 1), *text1 != '\0' ? text2 : (*text2 == '\0' ? text2 : text2 + 1), (_tmp::Dummy<M - 1> *)nullptr)
		{
			buffer_[N - 1 - M] = *text1 == '\0' ? *text2 : *text1;
		}

		constexpr fixed_text(const char *text1, const char *text2, _tmp::Dummy<0> *)
		{
			buffer_[N - 1] = '\0';
		}

		constexpr fixed_text(const char *text, int text_size)
			: fixed_text(text)
		{
			buffer_[ util::clamp(text_size, 0, N-1) ] = '\0';
		}

		constexpr int find_index(char c, int index) const
		{
			return index >= size() ? -1 : (buffer_[index] == c ? index : find_index(c, index + 1));
		}

		constexpr int rfind_index(char c, int index) const
		{
			return (index < 0) ? -1 : ( buffer_[index] == c ? index : rfind_index(c, index - 1));
		}

		constexpr bool is_match(const char*text, int index) const
		{
			return index < (size() - util::size_of(text))  && ( (*text == '\0') ||  ( buffer_[index] == *text && is_match(text + 1, index + 1) ));
		}

		constexpr bool is_match_any(const char* text, int index) const
		{
			return index < (size() - util::size_of(text)) && ( is_match(text, index) || is_match_any(text, index + 1) );
		}

	  private:
		char buffer_[N] = {0};
	};


#define FIXED_TEXT(Arg) ft::fixed_text<sizeof(Arg)>(Arg)
}

main.cpp

int main()
{
	constexpr auto text1 = FIXED_TEXT("HogeHoge");
	constexpr auto text2 = FIXED_TEXT("HogeHoge");
	static_assert(ft::util::equal("HogeHoge", "HogeHoge"), "util::equal");
	static_assert(ft::util::equal(text1.c_str(), text2.c_str()), "util::equal");
	static_assert(ft::util::size_of("Hoge") == 4, "size_of");

	constexpr auto text = FIXED_TEXT("HogeHoge");
	static_assert(text == "HogeHoge", "constructor");
	static_assert(text != "HogeHoge2", "assert");
	static_assert(text.remove_prefix(2) == "geHoge"		, "remove_prefix1");
	static_assert(text.remove_prefix("Hoge") == "Hoge"	, "remove_prefix2");
	static_assert(text.remove_suffix(2) == "HogeHo", "remove_suffix");
	static_assert(text.size() == 8, "size");
	static_assert(text.find_index('o') == 1, "find_index");
	static_assert(text.rfind_index('o') == 5, "rfind_index");
	static_assert(text.remove_prefix(text.find_index('o')) == "ogeHoge", "remove_prefix(find_index)"); // ogeHoge
	static_assert(text.is_match("Hoge"), "is_match1");
	static_assert(text.is_match("Hage") == false, "is_match2");
	static_assert(text.is_match_any("ge"), "is_match_any1");
	static_assert(text.is_match_any("eg") == false, "is_match_any2");
	static_assert(text.append( FIXED_TEXT("Hage")) == "HogeHogeHage", "append");
	static_assert(text.front() == 'H', "front");
	static_assert(text.back() == 'e', "back");
	static_assert(text[1] == 'o', "operator[]");
}
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?