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!)