関数内で 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 はじまってしまいましたね。