Ofek Gila
Ofek Gila

Reputation: 703

std::enable_if to change member *variable* declaration/type

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!

Upvotes: 3

Views: 1140

Answers (2)

ph3rin
ph3rin

Reputation: 4896

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

Upvotes: 0

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136515

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.
}

Upvotes: 6

Related Questions