C++ Templates That Accept Only Certain Types

C++ templates that accept only certain types

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.]

How do I restrict templates to specific types?

Yes you can, the most simple way for your example is to put a static_assert in your function.

template <typename T>
void writeLine(const T &ob)
{
static_assert(!std::is_same<T, float>::value, "You can't use floats here");
std::cout << ob << std::endl;
}

This will give you a compile time error if you try and use the function with a float.

Another option is called SFINAE and is used to make the template parameter deduction fail under certain circumstances.

Usually you use std::enable_if in combination with some templates from <type_traits> header. The same thing with SFINAE would look something like:

template <typename T, typename std::enable_if<!std::is_same<T, float>::value, int>::type = 0>
void writeLine(const T &ob)
{
std::cout << ob << std::endl;
}

Let's break down typename std::enable_if<!std::is_same<T, float>::value, int>::type.

!std::is_same<T, float>::value is the condition here, if this condition is true this template will have a ::type, otherwise it will not.

After the condition we can specify what we want the type to be if the condition is true, in this case we use int. If this is not specified it will default to void.

So as long as we don't pass a float to this template, the second template parameter will be deduced to int = 0. If we pass in a float the deduction will simply fail and you will get a no matching function error showing the template as a candidate with failed deduction.

C++ template accept only typenames with certain member

You have two questions:

  • Is it acceptable to refer in the template class to member of the typename T? (I mean implementation of getVersion())
  • Can I specify somehow that typename T requires to have such member?

The answer to both is "yes"

After I made your code into a compilable example, it does both of those things (try removing the definition of v from tD to see it reject that class)

Here is the code I came up with:

#include <stdio.h>

struct version {
char name[200];
int date;
};

class A {
public:
virtual void f() = 0;
virtual const version& getVersion() const = 0;
};

template <typename T>
class B : public A {
public:
// some methods implementation specific for class B that require using typename T

void f() override {};
const version& getVersion() const override { return T::v; }
};

class tC {
public:
static constexpr version v{"tC", 1};
// some methods for C
};

class tD {
public:
static constexpr version v{"tD", 2};
// some methods for D
};

int main()
{
B<tC> btc;
version vtc = btc.getVersion();
printf("%s %d\n",vtc.name,vtc.date);

B<tD> btd;
version vtd = btd.getVersion();
printf("%s %d\n",vtd.name,vtd.date);

return 0;
}

Try it here: https://onlinegdb.com/raF_cbBwN

How do I restrict a template class to certain built-in types?

One solution I've seen is to use std::enable_if in a type alias. Something like:

using value_type = typename std::enable_if<
std::is_same<float, RealType>::value ||
std::is_same<double, RealType>::value,
RealType
>::type;

value_type only exists if RealType is exactly float or double. Otherwise, the type is undefined and compilation fails.

I'd warn about being too strict with types, though. Templates are as powerful as they are partly because the duck typing they do means that any type that can be used the way you want to use it, will work. Disallowing types for the sake of disallowing types generally doesn't gain you much, and can make things less flexible than they could be. For example, you wouldn't be able to use a type with more precision, like a big-decimal type.

Template function that matches only certain types?

Use SFINAE with std::is_base_of:

template <typename T,
typename = std::enable_if_t<
std::is_base_of<Foo, T>::value
>>
void foo(T arg);

That will only include foo in the overload set if T inherits from Foo. Note that this includes ambiguous and inaccessible bases as well. If you want a solution that only allows for Ts that inherit publicly and unambiguously from Foo, then you can instead use std::is_convertible:

template <typename T,
typename = std::enable_if_t<
std::is_convertible<T*, Foo*>::value
>>
void foo(T arg);

Note the reversal of arguments.

Regardless of which form you pick, it can be aliased for brevity:

template <typename T>
using enable_if_foo = std::enable_if_t<std::is_base_of<Foo, T>::value>;

template <typename T,
typename = enable_if_foo<T>>
void foo(T arg);

This works because std::enable_if has a nested type named type if and only if the boolean passed in is true. So if std::is_base_of<Foo, T>::value is true, enable_if_t gets instantiated to void, as if we had written:

template <typename T,
typename = void>
void foo(T arg);

But, if T does not inherit from Foo, then the type trait will evaluate as false, and std::enable_if_t<false> is a substitution failure - there is no typename enable_if<false>::type. You might expect this to a compile error, but substitution failure is not an error (sfinae). It's just a template deduction failure. So the effect is that foo<T> is simply removed from the set of viable overload candidates in this case, no different from any other template deduction failure.

Restricting a range or similar concept to only accept a given type

Maybe something like this:

template <class R, class T>
concept range_of = std::ranges::range<R> &&
std::same_as<std::ranges::range_value_t<R>, T>;

static_assert(range_of<std::vector<int>, int>);

static_assert(range_of<decltype(std::declval<std::vector<int>&>() |
std::views::all),
int>);


Related Topics



Leave a reply



Submit