Matrefeytontias
Matrefeytontias

Reputation: 642

SFINAE to select constructor based on class value template parameter

I'm trying to write a class that exposes different constructors depending on the value of the class's own template parameters. The naive code that came to mind trying to do this is as follows :

// C++14
#include <type_traits>

template <int compile_time_w = -1, int compile_time_h = -1>
struct Grid
{
    template <std::enable_if_t<compile_time_w < 0 && compile_time_h < 0, int> = 0>
    Grid(int runtime_w, int runtime_h) : _w(runtime_w), _h(runtime_h) {}

    template <std::enable_if_t<compile_time_w < 0 && compile_time_h >= 0, int> = 0>
    Grid(int runtime_w) : _w(runtime_w), _h(compile_time_h) {}

    template <std::enable_if_t<compile_time_w >= 0 && compile_time_h < 0, int> = 0>
    Grid(int runtime_h) : _w(compile_time_w), _h(runtime_h) {}

    template <std::enable_if_t<compile_time_w >= 0 && compile_time_h >= 0, int> = 0>
    Grid() : _w(compile_time_w), _h(compile_time_h) {}

    int _w, _h;
};

int main()
{
    // Grid<2, 2> grid; // any combination of template parameters + constructor parameters fails to compile

    return 0;
}

Compiling the class without any instantiation of it works fine, but trying to instantiate it in any way or capacity always fails. The compilation error is always of the same format, and is reported for every constructor where SFINAE should trigger :

error: no type named ‘type’ in ‘struct std::enable_if’

Apparently std::enable_if is working as intended, but somehow what should not be considered as an error is. Any clue on what this is all about ?

Upvotes: 0

Views: 339

Answers (3)

bolov
bolov

Reputation: 75853

And the C++20 version:

template <int compile_time_w = -1, int compile_time_h = -1>
struct Grid
{
    Grid(int runtime_w, int runtime_h) requires (compile_time_w < 0 && compile_time_h < 0)
        : _w(runtime_w), _h(runtime_h) {}

    Grid(int runtime_w) requires(compile_time_w < 0 && compile_time_h >= 0)
        : _w(runtime_w), _h(compile_time_h) {}

    Grid(int runtime_h) requires(compile_time_w >= 0 && compile_time_h < 0)
        : _w(compile_time_w), _h(runtime_h) {}

    Grid() requires(compile_time_w >= 0 && compile_time_h >= 0)
        : _w(compile_time_w), _h(compile_time_h) {}

    int _w, _h;
};

Upvotes: 1

NathanOliver
NathanOliver

Reputation: 180955

In order to use SFINAE, the template parameters must be part of the current template. Since compile_time_w and compile_time_h are part of the class's template parameters, they are not usable. To fix it add them to each function template like

template <int compile_time_w = -1, int compile_time_h = -1>
struct Grid
{
    template <int compile_time_w_l = compile_time_w, int compile_time_h_l = compile_time_h, std::enable_if_t<compile_time_w_l < 0 && compile_time_w_l < 0, int> = 0>
    Grid(int runtime_w, int runtime_h) : _w(runtime_w), _h(runtime_h) {}

    template <int compile_time_w_l = compile_time_w, int compile_time_h_l = compile_time_h, std::enable_if_t<compile_time_w_l < 0 && compile_time_w_l >= 0, int> = 0>
    Grid(int runtime_w) : _w(runtime_w), _h(compile_time_h) {}

    template <int compile_time_w_l = compile_time_w, int compile_time_h_l = compile_time_h, std::enable_if_t<compile_time_w_l >= 0 && compile_time_w_l < 0, int> = 0>
    Grid(int runtime_h) : _w(compile_time_w), _h(runtime_h) {}

    template <int compile_time_w_l = compile_time_w, int compile_time_h_l = compile_time_h, std::enable_if_t<compile_time_w_l >= 0 && compile_time_w_l >= 0, int> = 0>
    Grid() : _w(compile_time_w), _h(compile_time_h) {}

    int _w, _h;
};

int main()
{
    Grid<2, 2> grid; // any combination of template parameters + constructor parameters fails to compile

    return 0;
}

Upvotes: 1

songyuanyao
songyuanyao

Reputation: 172994

SFINAE works with template parameters of function template; you should make constructor templates owning their own template parameters, and check them with std::enable_if instead of class template parameter; otherwise, with certain class template instantiation all the constructor templates would be instantiated and cause the error.

template <int w = compile_time_w, int h = compile_time_h, std::enable_if_t<w < 0 && h < 0, int> = 0>
Grid(int runtime_w, int runtime_h) : _w(runtime_w), _h(runtime_h) {}

template <int w = compile_time_w, int h = compile_time_h, std::enable_if_t<w < 0 && h >= 0, int> = 0>
Grid(int runtime_w) : _w(runtime_w), _h(compile_time_h) {}

template <int w = compile_time_w, int h = compile_time_h, std::enable_if_t<w >= 0 && h < 0, int> = 0>
Grid(int runtime_h) : _w(compile_time_w), _h(runtime_h) {}

template <int w = compile_time_w, int h = compile_time_h, std::enable_if_t<w >= 0 && h >= 0, int> = 0>
Grid() : _w(compile_time_w), _h(compile_time_h) {}

LIVE

Upvotes: 1

Related Questions