c++visual-studiotemplatesc++20requires-clause

requires clause for a non-template member function in a template class


I have a simple template class A. I want to enable a function if some requirements are satisfied.

Solution 1 - requires clause

The first solution I tried is the following:

template <class T>
class A
{
public:
    void a(void) requires (same_as<T, int>)
    {
        std::cout << "a" << std::endl;
    };
};

This works pretty well. I can call A<int>().a() but not A<char>().a();. Also, IntelliSense correctly identifies usage errors in Visual Studio.

I tried to move the function definition outside the class but I got C2511 compiler error in Visual Studio. In GCC, it works fine.

template <class T>
class A
{
public:
    void a(void) requires (same_as<T, int>);
};

template <class T>
void A<T>::a(void)
requires (same_as<T, int>)
{
    std::cout << "a" << std::endl;
}

Do you think my code is incorrect or is it a visual studio compiler bug/incomplete feature?


Solution 2 - static_assert

This solution works in some cases, but of course it would cause compile errors if you try an explicit template instantiation (for example template class A<char>). Also, IntelliSense would not correctly identify improper usages.

template <class T>
class A
{
public:

    void a(void);
};

template <class T>
void A<T>::a(void)
{
    static_assert(same_as<T, int>);
    std::cout << "a" << std::endl;
}

Soluton 3 - enable_if

(wrong)

template <class T>
class A
{
public:

    template <std::enable_if_t<same_as<T, int>, bool> = true>
    void a(void);
};

template <class T>
template <std::enable_if_t<same_as<T, int>, bool>>
void A<T>::a(void)
{
    std::cout << "a" << std::endl;
}

This solution has the same problems as #2. Moreover, I would prefer not to add some templates that are not understandable at first sight.


Solution 4 - some weird compile time inheritance

template <class T>
class A_base { /*common stuff*/ };

template <class T>
class A : public A_base<T>
{
public:
    A_base<T>::A_base;

    /* non-int stuff*/
};

template <>
class A<int> : public A_base<int>
{
public:
    A_base<int>::A_base;

    void a(void) {};
};

This would work well, but it could get quite complex in some situations, and very unpleasant for debugging when a bunch of levels are nested.


Do you have any advice / better solution?

Thanks in advance.


Edit:


Solution

  • Do you think my code is incorrect or is it a visual studio compiler bug/incomplete feature?

    The code is well-formed and as of 2022 is accepted by the latest version of all the three major compilers. Demo. The member function a is a templated entity as per temp.pre#8.3 and C++20 allows the use of requires-clause with a templated entity as per dcl.decl#4 which states:

    The optional requires-clause in an init-declarator or member-declarator shall be present only if the declarator declares a templated function ([dcl.fct]). When present after a declarator, the requires-clause is called the trailing requires-clause. The trailing requires-clause introduces the constraint-expression that results from interpreting its constraint-logical-or-expression as a constraint-expression.

    (emphasis mine)

    This means that the code given in solution 1 of your question is valid. For reference I am also posting the code below:

    template <class T>
    class A
    {
    public:
        void a(void) requires (same_as<T, int>);
    };
    //well-formed as per C++20
    template <class T>
    void A<T>::a(void)
    requires (same_as<T, int>)
    {
        std::cout << "a" << std::endl;
    }