2

I looked at a few similar questions, like this one and this other one, for example, and I understand how to work with enable_if for member functions.

Here is a working example:

#include <iostream>

template <int size>
class Test
{
private:
    constexpr static bool ENABLE = (size < 10);

public:
    template <bool E = ENABLE, typename std::enable_if<E, int>::type = 0>
    static int foo();

    template <bool E = ENABLE, typename std::enable_if<!E, int>::type = 0>
    constexpr static int foo();
};

template <int size>
template <bool E, typename std::enable_if<E, int>::type>
int Test<size>::foo()
{
    return 7;
}

template <int size>
template <bool E, typename std::enable_if<!E, int>::type>
constexpr int Test<size>::foo()
{
    return 12;
}

int main()
{
    Test<5> v1;
    Test<15> v2;

    std::cout << v1.foo() << "\n";
    std::cout << v2.foo() << "\n";
}

However, when I try to slightly modify the code to work for member variables, I get nasty redeclaration errors. Is this even possible to do with variables, am I just missing something simple?

Here is my problematic example code:

#include <iostream>

template <int size>
class Test
{
private:
    constexpr static bool ENABLE = (size < 10);

public:
    template <bool E = ENABLE, typename std::enable_if<E, int>::type = 0>
    static int foo;

    template <bool E = ENABLE, typename std::enable_if<!E, int>::type = 0>
    constexpr static int foo = 12;
};

template <int size>
template <bool E, typename std::enable_if<E, int>::type>
int Test<size>::foo = 7;

template <int size>
template <bool E, typename std::enable_if<!E, int>::type>
constexpr int Test<size>::foo;

int main()
{
    Test<5> v1;
    Test<15> v2;

    std::cout << v1.foo<> << "\n";
    std::cout << v2.foo<> << "\n";
}

Thanks in advance, any help/guidance is appreciated!

Ofek Gila
  • 693
  • 1
  • 10
  • 21
  • 1
    The presence of a variable cannot be controlled via `enable_if` – AndyG Oct 18 '19 at 16:49
  • Until then you're stuck with composing at the class level to get variables. Although it's an interesting proposition, conditionally include a variable via `requires` in C++ 20. I don't think we'd have any ODR violations, and it would be convenient. – AndyG Oct 18 '19 at 16:50
  • "Substitution Failure Is Not An Error" (or `SFINAE`), only `applies during overload resolution of function templates`. Clearly member variables aren't the case... – ph3rin Oct 18 '19 at 17:35

2 Answers2

6

You can achieve the desired effect with a conditional base class that provides member foo:

template<int FOO_INIT>
struct TestImpl1 {
    static int foo;
};

template<int FOO_INIT>
int TestImpl1<FOO_INIT>::foo = FOO_INIT;

template<int FOO_INIT>
struct TestImpl2 {
    constexpr static int foo = FOO_INIT;
};

template<int FOO_INIT>
constexpr int TestImpl2<FOO_INIT>::foo;

template<int size>
struct Test
    : std::conditional<
        (size < 10),
        TestImpl1<7>,
        TestImpl2<12>
      >::type
{};

int main() {
    Test<5> v1;
    Test<15> v2;

    std::cout << v1.foo << "\n";
    std::cout << v2.foo << "\n";

    // constexpr int i1 = v1.foo; // Fails to compile because Test<5>::foo is not constexpr.
    constexpr int i2 = v2.foo; // Compiles because Test<15>::foo is constexpr.
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • Thanks for this answer! My question is---what if I wanted "Test" to access / refer to foo in its member functions, how would that be done? – Ofek Gila Oct 22 '19 at 03:36
  • @OfekGila `this->foo`. Alternatively, `using Base = std::conditional_t<(size < 10), TestImpl1<7>, TestImpl2<12>>;` and then `Base::foo`. – Maxim Egorushkin Oct 22 '19 at 09:40
0

If you are only doing this on static member variables, there is still a chance you can make them work, by using block scoped static variables. This way your foo becomes a function, so you can apply SFINAE on them.

#include <iostream>

template <int size>
class Test
{
private:
    constexpr static bool ENABLE = (size < 10);

public:
    template <bool E = ENABLE, typename std::enable_if<E, int>::type = 0>
    static int& foo();

    template <bool E = ENABLE, typename std::enable_if<!E, int>::type = 0>
    constexpr static int foo();
};

template <int size>
template <bool E, typename std::enable_if<E, int>::type>
int& Test<size>::foo() {
    static int foo_impl = 7;
    return foo_impl;
}

template <int size>
template <bool E, typename std::enable_if<!E, int>::type>
constexpr int Test<size>::foo() {
    return 12;
}

int main()
{
    Test<5> v1;
    Test<15> v2;

    std::cout << v1.foo() << "\n";
    std::cout << v2.foo() << "\n";
}

Live demo

ph3rin
  • 4,426
  • 1
  • 18
  • 42