关于c ++:创建仅限专业化功能模板的最佳方法是什么?

What is the best way to create a specialization-only function template?

是否有更好的方法来执行以下操作?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

template <typename T>
T Bar();

template <>
int Bar<int>() { return 3; }

// Potentially other specialisations

int main()
{
    std::cout << Bar<int>() << std::endl; // This should work
    std::cout << Bar<float>() << std::endl; // This should fail
}

这个解决方案的问题是,它在(可以理解)链接时间失败,出现了"对float Bar()的未定义引用"之类的问题。这会让其他开发人员感到困惑,因为他们可能怀疑一个实现文件没有被链接。

我知道另一个可能的解决方案:

1
2
template <typename T>
T Bar() { BOOST_STATIC_ASSERT(sizeof(T) == 0); }

这会在请求Bar()时导致编译器错误,这正是我想要的。但是,我担心从技术上讲,编译器可能会拒绝这一点,就像GCC拒绝BOOST_STATIC_ASSERT(false)一样,因为它知道无论模板参数如何,它都会失败,因为sizeof(T)永远不能为零。

总之,我想知道:

  • 还有另一种方法可以做到这一点。
  • 我错了,没有实例化,BOOST_STATIC_ASSERT(sizeof(T))就不会失败。
  • 唯一的方法就是让这成为上面提到的链接器错误。

  • 这可能有效:

    1
    2
    3
    4
    5
    6
    7
    template <typename T>
    T Bar() {
      T::ERROR_invalid_template_argument_;
    }

    template <>
    int Bar<int>() { return 3; }

    如果您害怕使用0,也可以使用最大尺寸:

    1
      static_assert(sizeof(T) == -1,"No specialization");


    在模板被实例化之前,不允许BOOST_STATIC_ASSERT(sizeof(T) == 0);失败,所以我只做一个。你是正确的,每次都触发BOOST_STATIC_ASSERT(false);

    这与两阶段名称查找有关。这基本上是以下内容:当一个模板被编译时,它被编译了两次。第一次编译器看到模板时,它会编译除依赖于模板参数的表达式之外的所有内容,第二次编译在模板参数已知时进行,完全编译实例化。

    这就是为什么BOOST_STATIC_ASSERT(false);总是失败的原因:这里没有依赖项,断言被立即处理,就好像函数根本不是模板一样。(请注意,MSVC不执行两阶段查找,因此在实例化时失败,错误。)相反,因为T是依赖的(§14.6.2.1),BOOST_STATIC_ASSERT(sizeof(T) == 0);是依赖的,在模板实例化之前不允许检查。(在哪里总是失败。)

    如果一个编译器试图深思熟虑并提前失败,那么它将是不一致的。你应该可以依靠这些东西。也就是说,如果恐惧让你变得最好,那么真正让它等待是微不足道的:

    1
    BOOST_STATIC_ASSERT(sizeof(typename T::please_use_specializations) == 0);

    这既保证了失败,也不可能让编译器提前正确地"聪明地"失败。


    有一点需要注意的是,如果你使用GCC时不带学问,在这种情况下,当T是一个零长度的数组时,有可能使用sizeof(T) == 0

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #include <iostream>

    #include"boost/static_assert.hpp"

    template <typename T>
    void Foo()
    {
        BOOST_STATIC_ASSERT(sizeof(T) == 0);
        std::cout <<"Actually, it is possible to instantiate this." << std::endl;
    }

    int main()
    {
        Foo<int[0]>();

        return 0;
    }

    在这种情况下,您可以使用它来解决问题:

    1
    BOOST_STATIC_ASSERT(sizeof(T) == sizeof(T) + 1);

    最好将此技巧封装起来,这样可以提高可读性,因为它表达了您的意图:

    1
    #define NEVER_INSTANTIATE(T) BOOST_STATIC_ASSERT(sizeof(T) == sizeof(T) + 1);

    正如gman所解释的那样,如果没有像sizeof(T) == 0那样的实例化,这不会失败。然而,这个故事的寓意或许应该总是用学究来代替。


    使用C++0X使用STATICE-ASPART

    1
    2
    3
    4
    template <typename T>
    void bar(){
     static_assert(false," invalid argument type");
    }

    这将在编译时引发错误。

    对于C++ 98/2003,我们可以试试这个

    1
    2
    3
    4
    template <typename T>
    void bar(){
    char invalid_arg_[0];
    }

    数组至少包含一个元素。所以编译器会抱怨。但是错误消息可能无法显示发生了什么。

    第一个不会是一个选择,因为它总是失败的。


    您可以执行以下操作:

    1
    2
    3
    template <typename T>
    T Bar()
    { T::unspecialized_method_called; }

    当然,这假定没有具有给定名称的成员,因此您必须相应地选择"错误消息"(例如,违反命名约定)。