代码之家  ›  专栏  ›  技术社区  ›  Jorge Ferreira

模板约束C++

  •  86
  • Jorge Ferreira  · 技术社区  · 17 年前

    在C#中,我们可以定义一个泛型类型,对可用作泛型参数的类型施加约束。以下示例说明了通用约束的使用:

    interface IFoo
    {
    }
    
    
    class Foo<T> where T : IFoo
    {
    }
    
    class Bar : IFoo
    {
    }
    
    class Simpson
    {
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            Foo<Bar> a = new Foo<Bar>();
            Foo<Simpson> b = new Foo<Simpson>(); // error CS0309
        }
    }
    

    有没有一种方法可以在C++中对模板参数施加约束。


    C++0x对此有本机支持,但我说的是当前的标准C++。

    10 回复  |  直到 17 年前
        1
  •  33
  •   James Adkison    10 年前

    如果你使用C++11,你可以使用 static_assert 具有 std::is_base_of 为了这个目的。

    例如,

    #include <type_traits>
    
    template<typename T>
    class YourClass {
    
        YourClass() {
            // Compile-time check
            static_assert(std::is_base_of<BaseClass, T>::value, "type parameter of this class must derive from BaseClass");
    
            // ...
        }
    }
    
        2
  •  48
  •   Venemo    9 年前

    “含蓄地”是正确答案。由于模板的编译方式,模板有效地创建了一个“鸭子打字”场景。您可以在模板类型的值上调用任何您想要的函数,而唯一被接受的实例化是那些定义了该方法的实例化。例如:

    template <class T>
    int compute_length(T *value)
    {
        return value->length();
    }
    

    我们可以在指向任何类型的指针上调用此方法,该类型声明 length() 返回a的方法 int 因此:

    string s = "test";
    vector<int> vec;
    int i = 0;
    
    compute_length(&s);
    compute_length(&vec);
    

    …但不是指向某个类型的指针 声明 长度() :

    compute_length(&i);
    

    第三个示例无法编译。

    这之所以有效,是因为C++为每个实例化编译了一个新版本的模板化函数(或类)。当它执行编译时,它会在类型检查之前将模板实例化直接、近乎宏式地替换到代码中。如果所有内容仍然使用该模板,那么编译就会继续,我们最终会得到一个结果。如果有什么失败了(比如 int* 不申报 长度() ),然后我们会遇到可怕的六页模板编译时错误。

        3
  •  34
  •   Daniel Spiewak    17 年前

    正如其他人提到的,C++0x正在将此内置到语言中。在那之前,我会推荐 Bjarne Stroustrup s suggestions for template constraints .

    编辑: Boost 也有一个 alternative of its own .

    编辑2:看起来像 concepts have been removed from C++0x .

        4
  •  14
  •   Salman A    17 年前

    你可以在IFoo上设置一个不做任何事情的守卫类型,确保它在Foo的T上:

    class IFoo
    {
    public:
        typedef int IsDerivedFromIFoo;
    };
    
    template <typename T>
    class Foo<T>
    {
        typedef typename T::IsDerivedFromIFoo IFooGuard;
    }
    
        5
  •  8
  •   Félix Adriyel Gagnon-Grenier    11 年前

    查看 Boost

    Boost概念检查库(BCCL)

    概念检查库允许添加显式语句和检查 concepts 在风格上 proposed C++ language extension .

        6
  •  2
  •   Lou Franco    17 年前

    使用C++20,是的,有: Constraints and concepts

    也许你想保证模板是从特定类派生出来的:

    #include <concepts>
    
    template<class T, class U>
    concept Derived = std::is_base_of<U, T>::value;
    
    class ABase { };
    class ADerived : ABase { };
    
    template<Derived<ABase> T>
    class AClass {
        T aMemberDerivedFromABase;
    };
    

    然后,以下代码会像正常情况一样编译:

    int main () {
        AClass<ADerived> aClass;
        return 0;
    }
    

    但现在,当你违反合同时:

    class AnotherClass {
    
    };
    int main () {
        AClass<AnotherClass> aClass;
        return 0;
    }
    

    AnotherClass不是从ABase派生的,因此我的编译器(GCC)大致给出了以下错误:

    在函数“int main()”中:注意:未满足约束 注意:表达式'std::is_base_of<U、 T>*价值 [其中U=ABase;T=AnotherClass]'评估为“false” 9 |派生概念=std::is_base_of<U、 T>*价值;

    正如你所想象的,这个特性非常有用,可以做的不仅仅是约束一个类具有特定的基。

        7
  •  1
  •   shoosh    17 年前

    某种程度上。如果你将static_cast转换为IFoo*,那么就不可能实例化模板,除非调用者传递一个可以分配给IFoo*的类。

        8
  •  0
  •   Salman A    15 年前

    你可以做到的。创建基础模板。使其仅包含私有构造函数。然后为您想要允许的每种情况创建专门化(或者如果不允许的列表比允许的列表小得多,则进行相反的专门化)。

    编译器不允许您实例化使用私有构造函数版本的模板。

    此示例仅允许使用int和float进行实例化。

    template<class t> class FOO { private: FOO(){}};
    
    template<> class FOO<int>{public: FOO(){}};
    
    template<> class FOO<float>{public: FOO(){}};
    

    这不是一种简短而优雅的方式,但这是可能的。

        9
  •  -1
  •   RHss    17 年前

    只是含蓄地。
    在实际调用的方法中使用的任何方法都会强加在模板参数上。