0

I want to define a template class IE which provides public method(). method() calls underlying private run(), which may take arguments related to template parameters passed to IE. So:

  • For IE<void>, run() without arguments is executed.
  • For any other T in IE<T>, run(vector<T>) is executed.

I think I correctly SFINAE'd method run(), but I have problem with defining the parameter that should be passed to run. I came up with defining Extra in the presented way, but I get errors that T can't be deduced.

EDIT: I need a solution working for C++14 at most.

template<typename X=void>
class IE
{
    template<typename T=void>
    struct Extra;

    template<typename T>
    struct Extra<enable_if_t<is_void<T>::value, T>> {};

    template<typename T>
    struct Extra<enable_if_t<!is_void<T>::value, T>>
    {
        std::vector<T> ex;
    };

    template<typename X_=X>
    void run(enable_if_t<is_void<X_>::value , Extra<X_>> x) {
        cout << "In run" << endl;
    }

    template<typename X_ = X>
    void run(enable_if_t<!is_void<X_>::value , Extra<X_>> x)
    {
        cout << "In run: X=" << x.ex.size() << endl;
    }

public:
    void method()
    {
        Extra<X> x;
        run(x);
    }
};

int main() {
    IE<double> ie1;
    ie1.method(); // should execute run(vector<double>)

    IE<> ie2;
    ie2.method(); // should execute run()
    return 0;
}
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
Piotr G
  • 959
  • 1
  • 7
  • 25

3 Answers3

4

From your intent, you can do it with Constexpr if (since C++17). e.g.

template<typename X=void>
class IE
{

    void run() {
        cout << "In run()" << endl;
    }

    void run(std::vector<X> x)
    {
        cout << "In run: X=" << x.size() << endl;
    }

public:
    template<typename X_ = X>
    void method()
    {
        if constexpr (std::is_same_v<X_, void>)
            run();
        else
            run(std::vector<X_>{...});
    }
};

LIVE

Before C++17 you can apply SFINAE (or specialization), e.g.

template<typename X=void>
class IE
{

    void run() {
        cout << "In run()" << endl;
    }

    void run(std::vector<X> x)
    {
        cout << "In run: X=" << x.size() << endl;
    }

public:
    template<typename X_ = X>
    std::enable_if_t<std::is_same<X_, void>::value> method()
    {
        run();
    }
    template<typename X_ = X>
    std::enable_if_t<!std::is_same<X_, void>::value> method()
    {
        run(std::vector<X_>{...});
    }
};

LIVE

For your original solution, you should apply SFINAE as

template<typename X=void>
class IE
{
    template<typename T, typename = void>
    struct Extra;

    template<typename T>
    struct Extra<T, enable_if_t<is_void<T>::value>> {};

    template<typename T>
    struct Extra<T, enable_if_t<!is_void<T>::value>>
    {
        std::vector<T> ex;
    };

    template<typename X_ = X>
    enable_if_t<is_void<X_>::value> run(Extra<X_> x) {
        cout << "In run" << endl;
    }

    template<typename X_ = X>
    enable_if_t<!is_void<X_>::value> run(Extra<X_> x)
    {
        cout << "In run: X=" << x.ex.size() << endl;
    }

public:
    void method()
    {
        Extra<X> x;
        run(x);
    }
};

LIVE

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • Unfortunately, I am bound to c++14. Can you suggest another solution? – Piotr G May 15 '20 at 09:33
  • @PiotrG Answer revised. – songyuanyao May 15 '20 at 09:45
  • songyuanyao's code shows very nicely how such a thing can be implemented - I like the constexpr approach! Also take a look at https://stackoverflow.com/questions/6972368/stdenable-if-to-conditionally-compile-a-member-function (further explanation). – Wolfgang May 15 '20 at 09:48
  • This still doesn't work for me. I my case, method() is very long, and basically only run() is different. That is why I am using template solution: I wanted single method() and change only the part enclosed in run(). Now with two distinct methods() I don't get any benefit of using templates at all. – Piotr G May 15 '20 at 10:01
  • @PiotrG Then can't you simply refactor `method()` into a template parameter agnostic part and a template parameter dependent part? – G.M. May 15 '20 at 10:12
  • @PiotrG Like [this](https://wandbox.org/permlink/oF0uECAqOoE0ahBo)? – songyuanyao May 15 '20 at 10:14
  • @songyuanyao, yes, that finally works for me! Thanks! – Piotr G May 15 '20 at 10:19
0

It is your definition of Extra which is incorrect,

You might simply move it outside class and use regular specialization:

template<typename T>
struct Extra
{
    std::vector<T> ex;
};

template<>
struct Extra<void> {};

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

It may be that the actual code is more complicated, but for the code presented in the question, providing a specialization for IE<void> is the simplest approach. Get rid of all those enable_if things, and after definition of IE add the specialization:

template <>
class IE<void> {
    void run() { /* ... */ }
public:
    void method() { run(); }
};

(Yes, I'm not a fan of using constexpr if to write functions that have two or more completely independent execution paths depending on some type calculus; that's far too much like #ifdef ... #elif ... #endif)

Pete Becker
  • 74,985
  • 8
  • 76
  • 165