Home > Enterprise >  Full specialisation of a class template using a C 20 concept of a non-type template parameter
Full specialisation of a class template using a C 20 concept of a non-type template parameter

Time:03-02

I am fairly new to C 20 Concepts and I am experimenting with creating a template class Foo with fully specialised implementations based on a non-type (enumerator) template parameter. I have tested my idea using the code below:

// Foo.h
#include <concepts>
#include <iostream>
#include <type_traits>

enum class Type { A, B, C };

template<std::is_enum T1, std::is_enum T2>
constexpr bool isEnumSame() { return T1 == T2; }

template<Type T> concept isTypeA = isEnumSame<T, Type::A>;
template<Type T> concept isTypeB = isEnumSame<T, Type::B>;

template<Type T>
struct Foo
{
   Foo() { std::cout << "Generic Foo\n"; }
};

template<Type T>
requires isTypeA<T>
struct Foo<T>
{
   Foo() { std::cout << "Type A Foo\n"; }
};

template<Type T>
requires isTypeB<T>
struct Foo<T>
{
   Foo() { std::cout << "Type B Foo\n"; }
};
// Foo.cpp
#include "Foo.h"

int main(void)
{
   Foo<Type::C> testC;
   Foo<Type::A> testA;
   Foo<Type::B> testB;

   return 0;
}

However, I get compiler errors like the following (I'm using g 11 and gcc11):

error: insufficient contextual information to determine type
   27 |       Foo<Type::C> testC;

and similarly for Foo<Type::A> testA and Foo<Type::B> testB. Can someone advise me on what I may be doing incorrectly here? Also, I would ideally like to define the full specialisations similar to:

template<isTypeA T>
struct Foo<T>
{
   Foo() { Print("Type A Foo"); }
};

but the above yields the compiler error error: ‘isTypeA’ does not constrain a type. Any help would be much appreciated!

CodePudding user response:

std::is_enum is not a concept, so this predicate does not do what I assume you think it does:

template<std::is_enum T1, std::is_enum T2>
constexpr bool isEnumSame() { return T1 == T2; }

You can take Type values directly. Also, your concept lines need to call the functions like so:

template<Type T1, Type T2>
constexpr bool isEnumSame() { return T1 == T2; }

template<Type T> concept isTypeA = isEnumSame<T, Type::A>();
template<Type T> concept isTypeB = isEnumSame<T, Type::B>();

If you want your isEnumSame to be generic over any enumeration:

template <class T>
concept Enumeration = std::is_enum_v<T>;

template<Enumeration auto T1, Enumeration auto T2>
constexpr bool isEnumSame() { return T1 == T2; }

CodePudding user response:

There are several problems:

  1. template<std::is_enum T1, std::is_enum T2>
    

    is_enum is a trait, not a concept. You can use something like auto T1, and then requires std::is_enum_v<decltype(T1)>.

  2. template<Type T> concept isTypeA = isEnumSame<T, Type::A>;`
    

    You forgot to call the function, add ().


But you don't actually need any of this concept magic. You can fully specialize Foo for the enum values:

template <>
struct Foo<Type::A> {...};

If you for some reason don't want a full specialization, you can do this:

template <Type T>
requires (T == Type::A)
struct Foo<T> {...};

CodePudding user response:

First, std::is_enum is not a concept, it is just a normal class, so a syntax like template<std::is_enum T1, std::is_enum T2> just defines a template function that accepts a non-type template parameter of type std::is_enum, which is not what you might expect and doesn't constrain anything.

Second, the template parameter of bool isEnumSame() is not a type but a enum value, so it should be defined as template<auto T1, auto T2>.

If you need to constrain the type of the non-type template parameter, you can just use decltype to get its type and use requires-expression to constrain it, like this:

enum class Type { A, B, C };

template<auto T1, auto T2>
  requires std::is_enum_v<decltype(T1)> && std::is_enum_v<decltype(T2)>
constexpr bool isEnumSame() { return T1 == T2; }

template<Type T> concept isTypeA = isEnumSame<T, Type::A>();
template<Type T> concept isTypeB = isEnumSame<T, Type::B>();

Demo

  • Related