関数内で constexpr の場合のみ static_assert する

さて、constexpr を使っていると次のように関数内で static_assert を行いたい場合があると思います。

template<typename T>
void
check(T t){
    // t を参照して static_assert にしたい
    static_assert(t % 2 == 0, "");
}

constexpr int a = 2;
check(x);             // error: 't' is not a constant expression


しかし、上記のコードでは check 関数内で constexpr かどうかの判断が行えないのでコンパイルエラーになってしまいます。
これを回避する方法として、構造体内で constexpr 関数を定義する方法があります。

template<typename T>
void
check(T t){
    // operator int を参照する
    static_assert(t % 2 == 0, "");
}

struct X{
    constexpr operator int() const{
        return 2;
    }
};
constexpr X x{};
check(x);


これであれば問題なく動作します。
更に lambda expression 内でも constexpr 関数が記述できたので下記のようにも書くことが出来ました。

template<typename T>
void
check(T t){
    static_assert(t % 2 == 0, "");
}

// 関数の引数内で記述
check([]{
    struct X{
        constexpr operator int() const{
            return 10;
        }
    };
    return X{};
}());            // ok


#define constexpr_(expr)    \
    []{                     \
        struct X{           \
            constexpr operator decltype(expr)() const{    \
                return expr;                        \
            }               \
        };                  \
        return X{};         \
    }()

// マクロ関数を使用するとこんな感じ
check(constexpr_(2));        // ok
check(constexpr_(1));        // error

これは constexpr はじまってしまいましたね。
これを利用して簡単に type safe な printf を書いてみました。

[ソース]

#include <cstdio>
#include <type_traits>
#include <utility>

#define constexpr_(expr)    \
    []{                     \
        struct X{           \
            constexpr decltype(expr) operator()() const{    \
                return expr;                        \
            }                \
        };                   \
        return X{};          \
    }()

template<
    typename Char
>
constexpr bool
check_printf(Char const* f){
    return f[0] == '\0' ? true
         : f[0] == '%' && f[1] != '%' ? false
         : check_printf(f+1);
}


// とりあえず、%d だけチェック
template<
    typename Char,
    typename Head,
    typename... Args
>
constexpr bool
check_printf(Char const* f, Head const& head, Args const&... args){
    return f[0] != '%' || f[1] == '%'  ? check_printf(f+1, head, args...)
         : f[1] == 'd' && std::is_integral<Head>::value ? check_printf(f+1, args...)
         :  false;
}

template<
    typename T,
    typename ...Args
>
void
safe_printf(T t, Args... args){
    // t() の値で static_assert を行う
    static_assert(check_printf(t(), args...), "failed print format");
    
    // あとはそのまま printf の呼び出し
    printf(t(), args...);
}


int
main(){
    // 呼び出し側で static_assert をする場合
    static_assert( check_printf("%d", 10), "");
    static_assert(!check_printf("%c", 10), "");


    // 関数内で static_assert する
    // 問題ない場合は実行時に printf を呼ぶ
    safe_printf(constexpr_("%d"), 10);    // ok
//  safe_printf(constexpr_("%c"), 10);    // error: static assertion failed: "failed print format"

    return 0;
}

[出力]

10

フォーマットの文字列リテラルをラップする必要がありますが、呼び出し側で static_assert を書くよりはすっきりしていると思います。
これは constexpr はじまってしまいましたね。
C++11 たのしい。

[おまけ]

先日つくった is_constexpr と組み合わせると constexpr_() とそうじゃない場合で切り替える事が出来ます。

#include <cstdio>
#include <type_traits>

#define constexpr_(expr)    \
    []{                     \
        struct X{           \
            constexpr decltype(expr) operator()() const{    \
                return expr;                        \
            }                \
        };                   \
        return X{};          \
    }()


constexpr bool
true_(...){
    return true;
}

template<typename T, typename ...Args, bool = true_(T{}(Args{}...))>
constexpr bool
is_constexpr_impl(bool&&){
    return true;
}

template<typename T, typename ...Args>
constexpr bool
is_constexpr_impl(bool const&&){
    return false;
}

template<typename T, typename ...Args>
constexpr bool
is_constexpr(Args...){
    return is_constexpr_impl<T, Args...>(0);
}


template<
    typename FuncType,
    typename std::add_pointer<FuncType>::type Func,
    typename ...Args,
    bool = true_(Func(Args{}...))
>
constexpr bool
is_constexpr_impl(bool&&){
    return true;
}

template<
    typename FuncType,
    typename std::add_pointer<FuncType>::type Func,
    typename ...Args
>
constexpr bool
is_constexpr_impl(bool const&&){
    return false;
}


template<
    typename FuncType,
    typename std::add_pointer<FuncType>::type Func,
    typename ...Args
>
constexpr bool
is_constexpr(Args...){
    return is_constexpr_impl<FuncType, Func, Args...>(0);
}

template<
    typename Char
>
constexpr bool
check_printf(Char const* f){
    return f[0] == '\0' ? true
         : f[0] == '%' && f[1] != '%' ? false
         : check_printf(f+1);
}


// とりあえず、%d だけチェック
template<
    typename Char,
    typename Head,
    typename... Args
>
constexpr bool
check_printf(Char const* f, Head const& head, Args const&... args){
    return f[0] != '%' || f[1] == '%'  ? check_printf(f+1, head, args...)
         : f[1] == 'd' && std::is_integral<Head>::value ? check_printf(f+1, args...)
         :  false;
}

extern void* enabler;

// constexpr として評価できる場合のみ static_assert でチェックする
template<
    typename T,
    typename ...Args,
    typename std::enable_if<is_constexpr<T>()>::type *& = enabler
>
void
safe_printf(T t, Args... args){
    static_assert(check_printf(t(), args...), "failed print format");
    printf(t(), args...);
}


template<
    typename T,
    typename ...Args,
    typename std::enable_if<!is_constexpr<T>()>::type *& = enabler
>
void
safe_printf(T t, Args... args){
    printf(t, args...);
}


int
main(){
    safe_printf(constexpr_("%d"), 10);    // ok
//  safe_printf(constexpr_("%c"), 10);    // error: static assertion failed: "failed print format"
    
    // これは constexpr として扱わないのでコンパイルが通る
    safe_printf("%c", 10);                // ok
    return 0;
}

これは constexpr はじまってしまいましたね。

[コンパイラ]

  • g++ (GCC) 4.7.0 20111203 (experimental)