成员到指针变量仅在带有 -std=c++17 的 Clang 中被接受为函数模板参数

member-to-pointer variable only accepted as function template argument in Clang with -std=c++17

https://godbolt.org/z/o7rBT9

这在 -std=c++14

的 Clang 上编译得很好
struct Vecs
{
    enum class VecIndex { first, second };
    std::vector<int> vec1, vec2;

    template <VecIndex> struct vecForIndex;
    template <> struct vecForIndex<VecIndex::first> {
        static constexpr auto vecPtr = &Vecs::vec1;
    };
    template <> struct vecForIndex<VecIndex::second> {
        static constexpr auto vecPtr = &Vecs::vec2;
    };

    template <std::vector<int> Vecs::*vecPtr>
    static void work() {}

    static void workSpecificVec()  { 
        work<&Vecs::vec1>(); 
    }
};

但是这个版本的 workSpecificVec 没有:

static void workSpecificVec()  { 
    work<vecForIndex<VecIndex::first>::vecPtr>(); 
}

后者确实使用 -std=c++17 进行编译。

为什么?

P.S。 gcc 和 msvc 均未使用任何标准编译上述内容。 IIUC 在 class 范围 have been allowed since c++14 中的显式专业化,所以这似乎是编译器或我的理解的问题。

class 范围内的显式特化是 C++17 的一个特性 (CWG 727 was not a DR against C++14, it was new for C++17) that gcc just doesn't implement yet (this is gcc bug 85282)。

这里还有另一个 C++17 特性在发挥作用——扩大了哪些类型的参数可以用作非类型模板参数(N4198 for explanation, N4268 用于措辞)。在 C++14 中,vecForIndex<VecIndex::first>::vecPtr 不是允许的非类型模板参数,因为 只有 成员指针的允许参数(如您的示例)是:

  • &T::X 形式的精确表达式(就像字面上的语法)
  • 计算为空指针值的任意常量表达式。

就是这样。 vecForIndex<VecIndex::first>::vecPtr 两者都不是,所以它在 C++14 中的格式不正确。在 C++17 中,对非类型模板参数的限制要宽松得多,所以这样就可以了。


如果您希望所有这些都保留在 class 体内,您可以使用带有 if constexpr 的函数模板,而不是专门的 class 模板。代码也更少:

template <VecIndex I>
constexpr auto vecForIndex()
{
    if constexpr (I == VecIndex::first) return &Vecs::vec1;
    else if constexpr (I == VecIndex::second) return &Vecs::vec2;
}

然后:

static void workSpecificVec()  { 
    work<vecForIndex<VecIndex::first>()>(); 
}