autoで宣言した非型テンプレート引数の特殊化における罠(MSVC限定)

2023/11/14に公開2

環境

clangだとコンパイルが通りましたので、MSVCのみの現象と思われます。

概要

C++17以降では非型テンプレート引数にautoを使用できるようになっています。
(cpprefjp)

template<auto V> struct test {
  static constexpr auto value = N;
};

これをbool型で特殊化してみましょう。

template<bool B> struct test<B> {
  static constexpr auto value = B;
};

別に変なことはありませんよね?

しかし、MSVCの環境だと、以下のテストはエラーになります。

static_assert(test<true>::value == true);   // OK
static_assert(test<33>::value == 33);       // ERROR
static_assert(test<size_t(2)>::value == 2); // ERROR

調べてみると、すべてのテストでbool型での特殊化が適用されています。

試しにsize_t型の特殊化も追加して書いてみましょう。

template<size_t N> struct test<N> {
  static constexpr auto value = N;
};

エラーが出ました。

error C2752: test<true>: 1 つ以上の部分的特殊化がテンプレート引数リストと一致します。
error C2752: test<33>: 1 つ以上の部分的特殊化がテンプレート引数リストと一致します。
error C2752: test<2>: 1 つ以上の部分的特殊化がテンプレート引数リストと一致します。

(´・ω・`)

対応策

どうやらMSVCでは、「auto宣言した非型テンプレート引数」の「型」での特殊化は役に立たないようです。

対応としては、素直に

template<typename T, T V> struct test {
  static constexpr T value = V;
};
template<bool B> struct test<bool, B> {
  static constexpr auto value = B;
};

とする方法がありますが、値から型を判別できるのにわざわざ型を入力するのは煩わしいです。

可読性は低下しますが、以下のような書き方もできるでしょう。

template<auto V> struct test {
private:
  template<typename T, T W> struct impl {
    static constexpr auto value = W;
  };
  template<bool B> struct imple<bool, B> {
    static constexpr auto value = B;
  };
public:
  static constexpr auto value = imple<decltype(V), V>::value;
};

まとめ

以上、MSVCのバグと思われる挙動についての記事でした。

今回の例のようにbool型の特殊化をしてしまうと、入力したarithmetic型なパラメータはすべてbool型に暗黙的に変換されてしまうので、発見の難しい凶悪なバグだと思います。

Discussion