Ivan Smirnov
Ivan Smirnov

Reputation: 4435

How to ignore non-compilable methods in explicit template instantiation?

I have a template class. Some methods can only be called for certain template parameters, otherwise compilation error occurs. Here, if you call C<int>{}.vectorMethod() you get a compilation error that int does not have a .size() method.

template <class T>
struct C
{
    void intMethod()
    {
        cout << T{} + 1 << "\n";
    }
    void vectorMethod()
    {
        cout << T{}.size() << "\n";
    }
};

int main() {
    C<int> a;
    a.intMethod();
    // a.vectorMethod(); // compilation error

    C<vector<int>> b;
    b.vectorMethod();
}

I want to make an explicit instantiation of this template for some frequently used types and still be able to get a compilation error if I try to use a method unsuitable for a certain type. Ideally, I'd like to have the following:

// header.h
template <class T>
struct C
{
    void intMethod()
    {
        cout << T{} + 1 << "\n";
    }
    void vectorMethod()
    {
        cout << T{}.size() << "\n";
    }
};

extern template struct C<int>;
extern template struct C<vector<int>>;

// impl.cpp
#include "header.h"
template struct C<int>;
template struct C<vector<int>>;

// main.cpp
#include "header.h"

int main() {
    C<int> a;
    a.intMethod();
    // a.vectorMethod(); // compilation or linker error is desired

    C<vector<int>> b;
    b.vectorMethod();
}

The first problem is that explicit instantiation in impl.cpp fails to compile since it tries to instantiate all methods of the class. The second is that in main.cpp the compiler notes the extern template and does not try to compile C<int>::vectorMethod; that's why I'm fine with linker error instead of compilation one.

We can use concepts to get rid of the first problem:

template <class T>
struct C
{
    void vectorMethod()
    {
        if constexpr (requires { T{}.size(); }) {
            cout << T{}.size() << "\n";
        }
    }
};

Now impl.cpp compiles, but I can erroneously call C<int>::vectorMethod() and it will silently do nothing.

I also tried to use enable_if:

template <class T>
struct C
{
    std::enable_if_t<std::is_same_v<T, vector<int>>> vectorMethod()
    {
        cout << T{}.size() << "\n";
    }
};

However, template class C<int> also fails to compile in this case.

Is it possible to achieve what I'm trying to?

Upvotes: 0

Views: 67

Answers (1)

Jarod42
Jarod42

Reputation: 217255

With c++20 requires, you might do

template <class T>
struct C
{
    void intMethod() requires(requires {T{} + 1;})
    {
        std::cout << T{} + 1 << "\n";
    }
    void vectorMethod() requires(requires {T{}.size();})
    {
        std::cout << T{}.size() << "\n";
    }
};

Demo (Not sure why clang still fails).

Upvotes: 3

Related Questions