0

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 ?

Matrefeytontias
  • 592
  • 1
  • 4
  • 13
  • 1
    The class template parameters are not part of the immidiate context for the template constructors, so SFINAE is not applicable. – super May 01 '20 at 14:29
  • It's mentioned [here](https://en.cppreference.com/w/cpp/language/sfinae) under Explanation. – super May 01 '20 at 14:36

3 Answers3

1

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

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • *SFINAE works with template parameters of function template* Not really. You can SFINAE a class template as well. It's just that all the parameters need to be in the same template. – NathanOliver May 01 '20 at 14:36
  • @NathanOliver Isn't [SFINAE](https://en.cppreference.com/w/cpp/language/sfinae) used for overload resolution? *This rule applies during overload resolution of function templates:* – songyuanyao May 01 '20 at 14:40
  • It is, but its used for more than that. For instance you can combine partial specialization with it to get "generic specializations": https://stackoverflow.com/a/60399429/4342498 – NathanOliver May 01 '20 at 14:53
1

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;
}
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
1

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;
};
bolov
  • 72,283
  • 15
  • 145
  • 224
  • While I understand that you might not be able to use this as you specifically requested C++14, it can help others or yourself in the future. – bolov May 01 '20 at 14:42
  • I'm making my own framework so I honestly could. I'm just not sure what the implications are in terms of who's gonna be able to use it. – Matrefeytontias May 01 '20 at 14:47
  • @Matrefeytontias support for concepts is in the trunk of both gcc and clang. When users will switch to C++20 that's another matter. – bolov May 01 '20 at 14:48