8

In Java you can restrict generics so that the parameter type is only a subclass of a particular class. This allows the generics to know the available functions on the type.

I haven't seen this in C++ with templates. So is there a way to restrict the template type and if not, how does the intellisense know which methods are available for <typename T> and whether your passed-in type will work for the templated function?

user997112
  • 29,025
  • 43
  • 182
  • 361
  • 6
    If it walks like a duck, and quacks like a duck, why not just pretend it's a duck? – Benjamin Lindley Dec 18 '13 at 22:14
  • 1
    @BenjaminLindley with no type information how would intellisense be able to auto-complete functions/data members? (My life doesnt depend on intellisense, but you get the idea- how would the compiler know etc) – user997112 Dec 18 '13 at 22:15
  • Have a look at http://en.cppreference.com/w/cpp/header/type_traits you can use methods like is_same or is_base_of – gvd Dec 18 '13 at 22:17
  • There's `std::is_base_of`. – chris Dec 18 '13 at 22:18
  • 5
    @BenjaminLindley: That is one way of looking at interfaces the other metaphor would be: *A pistol and a camera share the same interface `shoot`, but I'd rather know what you have in your hands pointing at me* – David Rodríguez - dribeas Dec 18 '13 at 22:20
  • @BenjaminLindley "If it walks like a duck, and quacks like a duck, why not just pretend it's a duck?" Because that leads us away from the bondage that is C++ and Java, and is hence off-topic for this question. :) – Kaz Dec 18 '13 at 22:41
  • @DavidRodríguez-dribeas Ah right. Common Lisp fixes that problem; it is caused by the class scope disease. We create two different symbols in different packages: `weapons-package:shoot` and `photography-package:shoot`. Then use these symbols as names of generic functions. They are different, unrelated symbols. Classes don't contain methods, and they do not have scope, just slots (data storage locations) named by symbols (which can belong to any package, not necessarily the same one). – Kaz Dec 18 '13 at 22:53
  • @DavidRodríguez-dribeas And then, if we want to just use an unqualified `(shoot obj)` call, we just have to be clear about which `shoot` symbol we import into the package that is in effect where the call is being made. If we imported `shoot` from `weapons-package`, then at the time of that import we decided that `shoot` is the symbol `weapons-package::shoot`. A camera object likely won't take the call `(shoot camera)`, but a gun object will. – Kaz Dec 18 '13 at 22:55
  • @Kaz no: C++ `template`s are compile time duck typed. If it walks like a duck, and quacks like a duck, the `template` will treat it like a duck. C++ type erasure uses `template`s to run time duck type data (like `std::function` does) by creating custom automatically written wrappers for each compile time type into a type-erased version of themselves. Java Generics, on the other hand, are a mixture of an object that holds a fixed compile-time type (the base type of the "type argument") together with some machinery to automatically cast to/from that fixed compile-time type in some interfaces. – Yakk - Adam Nevraumont Dec 18 '13 at 22:58

7 Answers7

7

As of C++11, there is no way to constrain template type arguments. You can, however, make use of SFINAE to ensure that a template is only instantiated for particular types. See the examples for std::enable_if. You will want to use it with std::is_base_of.

To enable a function for particular derived classes, for example, you could do:

template <class T>
typename std::enable_if<std::is_base_of<Base, T>::value, ReturnType>::type 
foo(T t) 
{
  // ...
}

The C++ working group (in particular, Study Group 8) are currently attempting to add concepts and constraints to the language. This would allow you to specify requirements for a template type argument. See the latest Concepts Lite proposal. As Casey mentioned in a comment, Concepts Lite will be released as a Technical Specification around the same time as C++14.

Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324
  • 1
    Concepts Lite will not be part of the C++14 standard proper, it will be released as a separate Technical Specification in approximately the same time frame. Effectively compilers will not be required to implement Concepts Lite to be standard-conforming, but those that are interested will have a common specification to implement. – Casey Dec 18 '13 at 22:29
  • In general I prefer not to recommend SFINAE. In most cases it is not the right tool for the job, and end up causing more issues than what it solves. – David Rodríguez - dribeas Dec 18 '13 at 22:31
5

Use the static_assert with std::is_base_of

#include <type_traits>

class A {
};

class B: public A {
};

template <class T>
class Class1 {
    static_assert(std::is_base_of<A, T>::value, "T must be a descendant of A");
    ...
    T foo();
    ...
};


Class1<A> a; //it works
Class1<B> b; //it works
Class1<int> i; //compile error
mabg
  • 1,894
  • 21
  • 28
4

Just be specific

#include <iostream>
#include <typeinfo>

template <typename T>
struct IsSpecific
{
    void name() const { std::cout << typeid(T).name() << std::endl; }
};

template<typename T> struct Specific;
template <> struct Specific<char> : public IsSpecific<char>{};
template <> struct Specific<int> : public IsSpecific<int> {};
template <> struct Specific<long> : public IsSpecific<long>{};

int main() {
    Specific<char>().name();
    Specific<int>().name();
    Specific<long>().name();
    //Specific<std::string>().name(); // fails
}
  • would you be able to include some brief class definition just so I can see how this related to a typical class definition? – user997112 Dec 18 '13 at 22:21
4

Java generics and C++ templates are completely different things, the best thing you can do is avoiding attempting a 1-to-1 mapping. That being said, the question still remains valid, and the answer is not simple.

The simple approach used in most places is that the requirements on the type are part of the contract on the template. For example, std::sort requires that the first two arguments behave as RandomAccessIterators which is a documentation only interface (the properties are described, but there is no mechanism in code for this). Then the template just uses that information.

The second simplest approach would be to document that contract and provide static_asserts that verify what can be verified.

The next available step is to use SFINAE, which is a technique in which you force the compiler to check some features of the types that are being substituted. If the substitution (and check) fails, the template is dropped as invalid and the compiler moves on. I personally discourage most uses of SFINAE, it is a great tool, but it is often misused.

In a future standard there will be higher level constructs to enforce some form of an interface on the arguments to templates by means of Concepts. The problem is that it has proven to be hard to define how those constraints are to be defined, detected or verified and until a good solution is found the standard committee won't settle for a knowingly bad approach.

All that being said, I might want to refer back to the first paragraph here. There is a huge difference between Java generics and C++ templates. The latter provides something that is called compile time polymorphism. No real interface type is defined anywhere in the program there are [in general] no constraints that the types used in a template are related in any possible way.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
1

In C++, if you have some template argument T, then it is effectively constrained to be one of some set of classes by virtue of how it is used. For instance if you refer to T::foo somewhere in the template expansion, then T cannot possibly be a class which does not have a foo member. Or suppose T::foo does exist, but has the wrong type; your template does something like T::foo + 1, but the T::foo which is there is not arithmetic.

If T satisfies the template in every way, and the resulting instantiation makes sense, there is no reason to worry about it.

It is an important flexibility in C++ to be able to use a template with classes that are not related in any way (i.e. by inheritance).

Someone who needs to use the template just has to write a class whose structural features match the requirements of the template; the user of the template doesn't have to derive from certain types just to use them as arguments.

The only benefit to this kind of constraint would be clearer diagnosis. Rather than getting an error message about T::foo being inappropriate in some way, you might get "type Widget does not match parameter 2 of template Xyz."

However, though clearer, the diagnostic comes at the price of accepting the philosophy that this mismatch is in fact the real problem. (What if the programmer can just fix the foo member and make it work?)

Kaz
  • 55,781
  • 9
  • 100
  • 149
1

The way you'd do this in C++ would be to only use the template type in the input and output interfaces of your class.

struct base_pointer_container {
  std::vector<Base*> data;
  void append(Base* t) {
    data.push_back(t);
  }
  T* operator[](std::size_t n) const {
    Assert( n < data.size() );
    return data[n];
  }
};

template<typename T>
struct pointer_container:private base_pointer_container {
  void append(Base* t) {
    static_assert( std::is_base_of<Base,T>::value, "derivation failure" );
    return base_pointer_container::append(t);
  }
  T* operator[](std::size_t n) const {
    return static_cast<T*>(base_pointer_container::operator[](n));
  }
};

This is somewhat analogous to what Java does under the hood, if I understand their Generics properly: the Derived version is merely a thin wrapper around the Base version that does type casting on input/output operations.

C++ templates are much more than this. The entire class is written anew for each new set of type arguments, and the implementations can, in theory, be completely different. There is no relationship between a std::vector<int> and a std::vector<long> -- they are unrelated classes -- except that they can both be pattern-matched as std::vectors, and they share many properties.

This level of power is only rarely needed. But for an example of how it can be extremely powerful, most standard libraries use it to create type-erasure objects in std::function. std::function can take any language element for which operator() is defined and compatible with its signature, regardless of the run-time layout or design of the type, and erase (hide) the fact that its type is distinct.

So a function pointer, a lambda, a functor written by some programmer in tibet -- despite having no run time layout compatibilities, a std::function< bool() > can store all of them.

It (typically) does this by creating a custom holder object for each type T that has its own copy, move, construct, destruct and call code. Each of those is custom-compiled for the type in question. It then stores a copy of the object within itself, and exposes a virtual interface to each of these operations. std::function then holds a pointer to the parent interface of that custom object holder, and then exposes its "value-like" interface to the end user (you).

This mixture of pImpl and template duck type code generation and template constructors is known as type-erasure in C++, and it allows C++ to not have the common "object" root (with the entailed restrictions on run time object layout) that Java has without much in the way of sacrifice.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

An evolution of the actual type system adopted by C++ is on the way and it's named concepts.

The new C++1y concepts will probably provide what you are looking for, and since this feature was already planned for C++11 but didn't make it into the final draft, there is a gcc fork with an implementation of this concepts .

For now "the poor man" solution, if you want to stick with what is given to you by the standard, is to use type traits .

user2485710
  • 9,451
  • 13
  • 58
  • 102