Home > Net >  Fold expression: Replacing specific type but forwarding all others: How to achieve this?
Fold expression: Replacing specific type but forwarding all others: How to achieve this?

Time:07-12

I've trying to replace a specific type within a fold expression while simply forwarding all other types, but failed miserably.

As std::forward requires explicit template specialisation I tried providing another set of templated overloads for, but these haven't been considered for overload resolution and would, if that had worked, have led to ambiguous function calls anyway.

Second attempt was specialising std::forward but already failed in the attempt...

A simulation of std::forward; actually copied GCC's implementation of and just added a bit of output to see what's going on:

namespace test
{
template<typename T>
constexpr T&&
fw(typename std::remove_reference<T>::type& t) noexcept
{
    std::cout << "standard" << std::endl;
    return static_cast<T&&>(t);
}

template<typename T>
constexpr T&&
fw(typename std::remove_reference<T>::type&& t) noexcept
{
    std::cout << "standard (r-value)" << std::endl;
    static_assert
    (
            !std::is_lvalue_reference<T>::value,
            "template argument substituting T is an lvalue reference type"
    );
    return static_cast<T&&>(t);
}
}

My test is as follows:

class Test
{
public:
    template <typename ... T>
    void test(T&& ... t)
    {
        using test::fw;

///////////////////////////////////////////////////
        ( g(fw<T>(t)), ... );
///////////////////////////////////////////////////
    }

private:
    template <typename T>
    T&& g(T&& t)
    {
        std::cout << "g: r-value: " << t << '\n' << std::endl;
        return std::move(t);
    }
    template <typename T>
    T& g(T& t)
    {
        std::cout << "g: l-value " << t << '\n' << std::endl;
        return t;
    }
};

int main()
{
    int nn = 10;
    Test t;
    std::string s("daal");
    t.test(12, nn, std::string("alda"), s);

    return 0;
}

As is (and with my failed attempts, see linked questions) the output is:

standard
g: r-value: 12

standard
g: l-value 10

standard
g: r-value: alda

standard
g: l-value daal

Desired output would be something like:

standard
g: r-value: 12

standard
g: l-value 10

specialised (r-value)
g: r-value: alda

specialised (l-value)
g: l-value daal

(Reference type is optional.)

How could I achieve this?

Intention is to retain the fold-expression – I could solve the issue with recursive templates and appropriate overloads, but that's not the intention here. This is a question out of pure curiosity as I already solved my actual problem already (by discovering actually not needing the variadic template at all thus could work with if constexpr inside the function instead, and thus no XY-problem either – any more).

CodePudding user response:

if constexpr within a wrapper is the key to the solution (thanks @user17732522 for the hint), however it needs to be combined with a second std::forward and for not receiving only r-value[s| references] decltype(auto) as well. Such a function can even be included within the Test class, additionally desired but optional feature (a lambda within the test function might look like...).

A solution might look like:

class Test
{
    template <typename T>
///////////////////////////////////////////////////
// note: decltype(auto)!
///////////////////////////////////////////////////
    static decltype(auto) fw(T&& t)
    {
        if constexpr(std::is_same_v<std::remove_const_t<std::remove_reference_t<T>>, std::string>)
        {
            std::cout << "std::string: ";
            if constexpr(std::is_lvalue_reference_v<decltype(t)>)
            {
                std::cout << "l-value\n";
                std::string s;
                s.reserve(t.length()   2);
                s  = '\'';
                s  = t;
                s  = '\'';
                return s;
            }
            else
            {
                std::cout << "r-value\n";
                t.reserve(t.length()   2);
                t.insert(t.begin(), '\'');
                t.push_back('\'');
                return t;
            }
        }
        else
        {
            std::cout << "default\n";
            return std::forward<T>(t);
        }
    }

public:
    template <typename ... T>
    void test(T&& ... t)
    {
///////////////////////////////////////////////////
// note: yet another call to std::forward!
///////////////////////////////////////////////////
        ( g(fw(std::forward<T>(t))), ... );
    }

private:
    template <typename T>
    T&& g(T&& t)
    {
        std::cout << "g: r-value: " << t << '\n' << std::endl;
        return std::move(t);
    }
    template <typename T>
    T& g(T& t)
    {
        std::cout << "g: l-value " << t << '\n' << std::endl;
        return t;
    }
};

int main()
{
    int n = 10;
    Test t;
    std::string s("daal");
    t.test(12, n, std::string("alda"), s);
    std::cout << std::endl;

    return 0;
}

(Modifying the strings simulates conversion to another type...)

Received output (as desired):

default
g: r-value: 12

default
g: l-value 10

std::string: r-value
g: l-value 'alda'

std::string: l-value
g: r-value: 'daal'

(auto instead of decltype(auto) prints g: r-value four times!)

  • Related