web-dev-qa-db-ja.com

C ++:マクロは「abc」を「a」、「b」、「c」に展開できますか?

可変数のcharパラメータを受け入れる可変個引数テンプレートを作成しました。

template <char... Chars>
struct Foo;

次のような構文でこれをインスタンス化できるマクロトリックがあるかどうか疑問に思っていました。

Foo<"abc">

または

Foo<SOME_MACRO("abc")>

または

Foo<SOME_MACRO(abc)>

等.

基本的に、あなたが文字を個別に書く必要を妨げるものは何でもそうです

Foo<'a', 'b', 'c'>

おもちゃのプログラムなので大した問題ではありませんが、とにかくお願いしたいと思いました。

40
Peter Alexander

今日作成し、GCC4.6.0でテストしました。

#include <iostream>

#define E(L,I) \
  (I < sizeof(L)) ? L[I] : 0

#define STR(X, L)                                                       \
  typename Expand<X,                                                    \
                  cstring<E(L,0),E(L,1),E(L,2),E(L,3),E(L,4), E(L,5),   \
                          E(L,6),E(L,7),E(L,8),E(L,9),E(L,10), E(L,11), \
                          E(L,12),E(L,13),E(L,14),E(L,15),E(L,16), E(L,17)> \
                  cstring<>, sizeof L-1>::type

#define CSTR(L) STR(cstring, L)

template<char ...C> struct cstring { };

template<template<char...> class P, typename S, typename R, int N>
struct Expand;

template<template<char...> class P, char S1, char ...S, char ...R, int N>
struct Expand<P, cstring<S1, S...>, cstring<R...>, N> :
  Expand<P, cstring<S...>, cstring<R..., S1>, N-1>{ };

template<template<char...> class P, char S1, char ...S, char ...R>
struct Expand<P, cstring<S1, S...>, cstring<R...>, 0> {
  typedef P<R...> type;
};

いくつかのテスト

template<char ...S> 
struct Test {
  static void print() {
    char x[] = { S... };
    std::cout << sizeof...(S) << std::endl;
    std::cout << x << std::endl;
  }
};

template<char ...C>
void process(cstring<C...>) {
  /* process C, possibly at compile time */
}

int main() {
  typedef STR(Test, "Hello folks") type;
  type::print();

  process(CSTR("Hi guys")());
}

したがって、'a', 'b', 'c'を取得しなくても、コンパイル時の文字列を取得できます。

たくさんの試練がありましたが、最終的には失敗する運命にあると思います。

その理由を理解するには、プリプロセッサがどのように機能するかを理解する必要があります。プリプロセッサの入力は、ストリームと考えることができます。このストリームは、最初にpreprocessing-tokensに変換されます(C++プログラミング言語、第3版、付録A文法、795ページで利用可能なリスト)

これらのトークンでは、プリプロセッサは、ダイグラム/トリグラムのものを除いて、非常に制限された数の操作のみを適用できます。これは次のようになります。

  • ファイルインクルード(ヘッダーディレクティブの場合)、これは私が知る限りマクロに表示されない場合があります
  • マクロ置換(これは非常に複雑なものです:p)
  • #:トークンをstring-literalトークンに変換します(引用符で囲むことにより)
  • ##:2つのトークンを連結します

以上です。

  • トークンを複数のトークンに分割する可能性のあるプリプロセッサ命令はありません。これはマクロ置換です。つまり、最初にマクロを実際に定義する必要があります。
  • string-literalを通常のトークンに変換する(引用符を削除する)プリプロセッサ命令はありません。このトークンは、マクロ置換の対象となる可能性があります。

したがって、私は、これには(おそらく)コンパイラ固有の拡張機能があるかもしれませんが、(C++ 03またはC++ 0xのいずれかで)不可能であると主張します。

9
Matthieu M.

上記のSylvainDefresneの応答に基づく解決策は、C++ 11で可能です。

#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>

template <unsigned int N>
constexpr char get_ch (char const (&s) [N], unsigned int i)
{
    return i >= N ? '\0' : s[i];
}

#define STRING_TO_CHARS_EXTRACT(z, n, data) \
        BOOST_PP_COMMA_IF(n) get_ch(data, n)

#define STRING_TO_CHARS(STRLEN, STR)  \
        BOOST_PP_REPEAT(STRLEN, STRING_TO_CHARS_EXTRACT, STR)

// Foo <STRING_TO_CHARS(3, "abc")>
//   expands to
// Foo <'a', 'b', 'c'>

さらに、問題のテンプレートが複数の終了 '\ 0'文字を処理できる場合は、最大長を優先して長さの要件を緩和できます。

#define STRING_TO_CHARS_ANY(STR) \
        STRING_TO_CHARS(100, STR)

// Foo <STRING_TO_CHARS_ANY("abc")>
//   expands to
// Foo <'a', 'b', 'c', '\0', '\0', ...>

上記の例は、clang ++(3.2)およびg ++(4.8.0)で正しくコンパイルされます。

9
user1653543

これは以前のバージョンのmsvcで機能していましたが、まだ機能するかどうかはわかりません。

#define CHAR_SPLIT(...) #@__VA_ARGS__
4
P47RICK

上記のser165354のソリューションに基づいています。

いくつかのテンプレートマジック:

_template <unsigned int N>
constexpr char getch (char const (&s) [N], unsigned int i)
{
    return i >= N ? '\0' : s[i];
}

template<char ... Cs>
struct split_helper;

template<char C, char ... Cs>
struct split_helper<C, Cs...>
{
    typedef Push_front_t<typename split_helper<Cs...>::type, char_<C>> type;
};

template<char ... Cs>
struct split_helper<'\0', Cs...>
{
    typedef std::integer_sequence<char> type;
};

template<char ... Cs>
using split_helper_t = typename split_helper<Cs...>::type;
_

いくつかのPP魔法:

_#define SPLIT_CHARS_EXTRACT(z, n, data) \
    BOOST_PP_COMMA_IF(n) getch(data, n)

#define STRING_N(n, str) \
    split_helper_t<BOOST_PP_REPEAT(n, SPLIT_CHARS_EXTRACT, str)>

#define STRING(str) STRING_N(BOOST_PP_LIMIT_REPEAT, str)
_

_split_helper_末尾のゼロをカットするための単なるヘルパー。現在、STRING("Hello")は型指定されたコンパイル時のcharシーケンス(_std::integer_sequence<char, 'H', 'e', 'l', 'l', 'o'>_)です。文字列定数の長さは最大_BOOST_PP_LIMIT_REPEAT_文字です。

宿題:_Push_front_t_と_c_str_を実装して、_std::integer_sequence<char, ...>_のnullで終了する文字列を取得します。 (ただし、Boost.MPLの使用を試みることはできます)

4
Nevermore

残念ながら、これはできないと思います。プリプロセッサから得られる最高のものは、 Boost.Preprocessor によって提供されます。特に、そのデータ型によって提供されます。

  • array :構文は(3, (a, b, c))
  • list :構文は(a, (b, (c, BOOST_PP_NIL)))
  • sequence :構文は(a)(b)(c)
  • Tuple :構文は(a, b, c)

これらのタイプのいずれかから、一重引用符で囲まれたアイテムのコンマ区切りリストを作成するマクロを簡単に作成できます(たとえば、 BOOST_PP_SEQ_ENUM )ですが、このマクロの入力はこれらのタイプの1つである必要があり、すべて文字を個別に入力する必要があると思います。

3
icecrime

私が上で議論したことに基づいて、次のひどいテンプレートハッカーはこれをやってのけるのに十分かもしれません。私はこれをテストしていません(申し訳ありません!)が、それまたはそれに近いものが機能する可能性があると確信しています。

最初のステップは、文字のタプルを保持するだけのテンプレートクラスを作成することです。

template <char... Chars> class CharTuple {};

それでは、Cスタイルの文字列をCharTupleに変換できるアダプタを作成しましょう。これを行うには、基本的にタプルのLISPスタイルの短所である次のヘルパークラスが必要です。

template <typename Tuple, char ch> class Cons;
template <char... Chars, char ch> class Cons<CharTuple<Chars... ch>> {
    typedef CharTuple<ch, Chars...> type;
}

また、meta-ifステートメントがあると仮定しましょう。

template <bool Condition, typename TrueType, typename FalseType> class If {
    typedef typename TrueType::type type;
};
template <typename TrueType, typename FalseType> class If<False> {
    typedef typename FalseType::type type;
};

次に、Cスタイルの文字列をタプルに変換できるようにする必要があります。

template <typename T> class Identity {
    typedef T type;
};

template <char* str> class StringToChars {
    typedef typename If<*str == '\0', Identity<CharTuple<>>,
                        Cons<*str, typename StringToChars<str + 1>::type>>::type type;
};

Cスタイルの文字列をcharのタプルに変換できるようになったので、入力文字列をこのタイプにファネルしてタプルを復元できます。ただし、これを機能させるには、もう少し機械を実行する必要があります。 TMPは面白くないですか? :-)

最初のステップは、元のコードを取得することです。

template <char... Chars> class Foo { /* ... */ };

テンプレートの特殊化を使用して、に変換します

template <typename> class FooImpl;
tempalte <char... Chars> class FooImpl<CharTuple<Chars...>> { /* ... */ };

これは、間接参照のもう1つの層です。これ以上何もない。

最後に、これを行うことができるはずです:

template <char* str> class Foo {
    typedef typename FooImpl<typename StringToChars<str>::type>::type type;
};

私はこれがうまくいくことを本当に望んでいます。そうでない場合でも、おそらく有効な答えに近いので、投稿する価値があると思います。 :-)

2
templatetypedef

C++ 14では、これは、 _BOOST_HANA_STRING_ のように、すぐに呼び出されるラムダと静的メンバー関数を使用して実行できます。

_#include <utility>

template <char... Cs>
struct my_string {};

template <typename T, std::size_t... Is>
constexpr auto as_chars_impl(std::index_sequence<Is...>) {
    return my_string<T::str()[Is]...>{};
}

template <typename T>
constexpr auto as_chars() {
    return as_chars_impl<T>(
        std::make_index_sequence<sizeof(T::str())-1>{});
}

#define STR(literal)                                        \
    []{                                                     \
        struct literal_to_chars {                           \
            static constexpr decltype(auto) str() {         \
                return literal;                             \
            }                                               \
        };                                                  \
        return as_chars<literal_to_chars>();                \
    }()
_

ゴッドボルトに住む

C++ 17より前は、ラムダをconstexprにすることはできないため、STR("some literal")によって返されるオブジェクトをconstexprにすることはできません。 C++ 20より前は、ラムダは未評価のコンテキストでは許可されていないため、decltype(STR("some literal"))を記述することはできません。

0
Justin