C++ templates that accept only certain types

Viewed 180544

In Java you can define generic class that accept only types that extends class of your choice, eg:

public class ObservableList<T extends List> {
  ...
}

This is done using "extends" keyword.

Is there some simple equivalent to this keyword in C++?

14 Answers

I suggest using Boost's static assert feature in concert with is_base_of from the Boost Type Traits library:

template<typename T>
class ObservableList {
    BOOST_STATIC_ASSERT((is_base_of<List, T>::value)); //Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
    ...
};

In some other, simpler cases, you can simply forward-declare a global template, but only define (explicitly or partially specialise) it for the valid types:

template<typename T> class my_template;     // Declare, but don't define

// int is a valid type
template<> class my_template<int> {
    ...
};

// All pointer types are valid
template<typename T> class my_template<T*> {
    ...
};

// All other types are invalid, and will cause linker error messages.

[Minor EDIT 6/12/2013: Using a declared-but-not-defined template will result in linker, not compiler, error messages.]

The simple solution, which no one have mentioned yet, is to just ignore the problem. If I try to use an int as a template type in a function template that expects a container class such as vector or list, then I will get a compile error. Crude and simple, but it solves the problem. The compiler will try to use the type you specify, and if that fails, it generates a compile error.

The only problem with that is that the error messages you get are going to be tricky to read. It is nevertheless a very common way to do this. The standard library is full of function or class templates that expect certain behavior from the template type, and do nothing to check that the types used are valid.

If you want nicer error messages (or if you want to catch cases that wouldn't produce a compiler error, but still don't make sense) you can, depending on how complex you want to make it, use either Boost's static assert or the Boost concept_check library.

With an up-to-date compiler you have a built_in static_assert, which could be used instead.

As far as I know this isn't currently possible in C++. However, there are plans to add a feature called "concepts" in the new C++0x standard that provide the functionality that you're looking for. This Wikipedia article about C++ Concepts will explain it in more detail.

I know this doesn't fix your immediate problem but there are some C++ compilers that have already started to add features from the new standard, so it might be possible to find a compiler that has already implemented the concepts feature.

C++20 concept usage example

Adapted from https://en.cppreference.com/w/cpp/language/constraints, you could just do some duck typing:

#include <cassert>
#include <concepts>

struct ClassWithMyFunc {
    int myFunc() {
        return 1;
    }
};

struct ClassWithoutMyFunc {};

// Concept HasMyFunc: type 'T' has `.myFunc` and
// its return is convertible to int.
template<typename T>
concept HasMyFunc= requires(T a) {
    { a.myFunc() } -> std::convertible_to<int>;
};

// Constrained function template
template<HasMyFunc T>
int f(T t) {
    return t.myFunc() + 1;
}

int main() {
    assert(f(ClassWithMyFunc()) == 2);
    // assert(f(ClassWithoutMyFunc()) == 2);
}

Compile and run:

g++ -ggdb3 -O0 -std=c++20 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

If we uncomment the line // assert(f(ClassWithoutMyFunc()) == 2);, it fails as desired with:

In file included from /usr/include/c++/10/cassert:44,
                 from main.cpp:1:
main.cpp: In function ‘int main()’:
main.cpp:27:34: error: use of function ‘int f(T) [with T = ClassWithoutMyFunc]’ with unsatisfied constraints
   27 |     assert(f(ClassWithoutMyFunc()) == 2);
      |                                  ^
main.cpp:21:5: note: declared here
   21 | int f(T t) {
      |     ^
main.cpp:21:5: note: constraints not satisfied
main.cpp: In instantiation of ‘int f(T) [with T = ClassWithoutMyFunc]’:
main.cpp:27:5:   required from here
main.cpp:15:9:   required for the satisfaction of ‘HasMyFunc<T>’ [with T = ClassWithoutMyFunc]
main.cpp:15:20:   in requirements with ‘T a’ [with T = ClassWithoutMyFunc]
main.cpp:16:15: note: the required expression ‘a.myFunc()’ is invalid
   16 |     { a.myFunc() } -> std::convertible_to<int>;
      |       ~~~~~~~~^~
cc1plus: note: set ‘-fconcepts-diagnostics-depth=’ to at least 2 for more detail

Require multiple base classes

If you really want to require one of certain base classes:

#include <concepts>
#include <type_traits>

struct Base1 {};
struct Base2 {};

struct Derived1 : public Base1 {};
struct Derived2 : public Base2 {};

struct NotDerived {};

template<typename T>
concept HasBase1Or2= std::is_base_of<Base1, T>::value || std::is_base_of<Base2, T>::value;

template<HasBase1Or2 T>
void f(T) {}

int main() {
    f(Derived1());
    f(Derived2());
    // f(NotDerived());
}

If we uncomment the line // f(NotDerived()); it fails as desired with:

main.cpp: In function ‘int main()’:
main.cpp:22:19: error: use of function ‘void f(T) [with T = NotDerived]’ with unsatisfied constraints
   22 |     f(NotDerived());
      |                   ^
main.cpp:17:6: note: declared here
   17 | void f(T) {}
      |      ^
main.cpp:17:6: note: constraints not satisfied
main.cpp: In instantiation of ‘void f(T) [with T = NotDerived]’:
main.cpp:22:19:   required from here
main.cpp:13:9:   required for the satisfaction of ‘HasBase1Or2<T>’ [with T = NotDerived]
main.cpp:13:55: note: no operand of the disjunction is satisfied
   13 | concept HasBase1Or2= std::is_base_of<Base1, T>::value ||
      |                                                 ~~~~~~^~
   14 |                      std::is_base_of<Base2, T>::value;
      |                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
cc1plus: note: set ‘-fconcepts-diagnostics-depth=’ to at least 2 for more detail

Tested on Ubuntu 21.04 GCC 10.3.0.

GCC 10 appears to have implemented it: https://gcc.gnu.org/gcc-10/changes.html and you can get it as a PPA on Ubuntu 20.04. https://godbolt.org/ GCC 10.1 did not recognize concept however on Ubuntu 20.04.

That's not possible in plain C++, but you can verify template parameters at compile-time through Concept Checking, e.g. using Boost's BCCL.

As of C++20, concepts are becoming an official feature of the language.

Is there some simple equivalent to this keyword in C++?

No.

Depending on what you're trying to accomplish, there might be adequate (or even better) substitutes.

I've looked through some STL code (on linux, I think it's the one deriving from SGI's implementation). It has "concept assertions"; for instance, if you require a type which understands *x and ++x, the concept assertion would contain that code in a do-nothing function (or something similar). It does require some overhead, so it might be smart to put it in a macro whose definition depends on #ifdef debug.

If the subclass relationship is really what you want to know about, you could assert in the constructor that T instanceof list (except it's "spelled" differently in C++). That way, you can test your way out of the compiler not being able to check it for you.

Well, you could create your template reading something like this:

template<typename T>
class ObservableList {
  std::list<T> contained_data;
};

This will however make the restriction implicit, plus you can't just supply anything that looks like a list. There are other ways to restrict the container types used, for example by making use of specific iterator types that do not exist in all containers but again this is more an implicit than an explicit restriction.

To the best of my knowledge a construct that would mirror the statement Java statement to its full extent does not exist in current standard.

There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.

In C++11, the introduction of concepts should make this easier but I don't think it'll do exactly what you'd want either.

Related