我是否正确地排除了 base 的 fn() 函数?

Am I correctly excluding the base's fn() function?

我正试图做一些有趣的事情,但它在 VC++ 2015. I tried it in clang and g++ 上崩溃了,没有编译或运行时错误。 (链接指向 rextester.com 演示)

#include <type_traits>
#include <iostream>

int fn1()    { return 1; }
int fn2(int) { return 2; }

template <typename FN_T, FN_T FN, typename DERIVED>
class test_base
{
    template <typename T = DERIVED, typename = std::enable_if_t<!T::fn_specified>>
    int fn()
    {
        return FN();
    }

public:
    int fn_indirect()
    {
        return static_cast<DERIVED*>(this)->fn();
    }
};

template <typename FN_T, FN_T FN, typename ENABLER = void>
class test_derived : public test_base<FN_T, FN, test_derived<FN_T, FN>>
{
public:
    static constexpr bool fn_specified = false;
};

template <typename FN_T, FN_T FN>
class test_derived<FN_T, FN, std::enable_if_t<FN == &fn2>>
    : public test_base<FN_T, FN, test_derived<FN_T, FN>>
{
    using base = test_base<FN_T, FN, test_derived<FN_T, FN>>;
    friend base;

public:
    static constexpr bool fn_specified = true;
    int fn()
    {
        return FN(1);
    }
};

int main(void)
{
    test_derived<decltype(&fn1), &fn1> x1;
    test_derived<decltype(&fn2), &fn2> x2;

    std::cout << x1.fn_indirect() << " ";

    // comment next line out and it'll work in VC++
    std::cout << x2.fn_indirect() << std::endl;
    return 0;
}

这个想法是,如果我在派生的 class 中指定一个 fn(),那么基础 class 将不会实例化该函数,因此不会由于以下原因而导致错误调用 FN() 时参数过多,当 FN() 指向特定函数时(在本例中为 fn2(int))。

我这样做是否正确,还是我遗漏了什么?

您使用的结构称为 Expression SFINAE。 Visual Studio 直到 2017 年才声明为 support it. Visual Studio 2015's updates enables partial support (read more about update 1, updates 1, 2 and 3)。然而,要使其在所有版本的 MSVC 中工作,需要创建不涉及表达式的变通方法,例如内部模板专业化参数。要在您的情况下实现它,可以使用带有附加间接层的模式匹配(例如 std::integral_constant):

#include <type_traits>
#include <iostream>

int fn1()    { return 1; }
int fn2(int) { return 2; }

template <typename FN_T, FN_T FN, typename DERIVED>
class test_base
{
    template <typename T = DERIVED, typename = std::enable_if_t<!T::fn_specified>>
    int fn()
    {
        return FN();
    }

public:
    int fn_indirect()
    {
        return static_cast<DERIVED*>(this)->fn();
    }
};

template <class FN_T>
class test_derived : public test_base<typename FN_T::value_type, FN_T::value, test_derived<FN_T>>
{
public:
    static constexpr bool fn_specified = false;
};

template <typename FN_T>
class test_derived<std::integral_constant<FN_T, &fn2>>
    : public test_base<FN_T, &fn2, test_derived<std::integral_constant<FN_T, &fn2>>>
{
    using base = test_base<FN_T, &fn2, test_derived<std::integral_constant<FN_T, &fn2>>>;
    friend base;

public:
    static constexpr bool fn_specified = true;
    int fn()
    {
        return fn2(1);
    }
};

int main(void)
{
    test_derived<std::integral_constant<decltype(&fn1), &fn1>> x1;
    test_derived<std::integral_constant<decltype(&fn2), &fn2>> x2;

    std::cout << x1.fn_indirect() << " ";

    // comment next line out and it will work
    std::cout << x2.fn_indirect() << std::endl;
    return 0;
}

[live demo]