That's an argument against templating of virtual functions, not against
argument forwarding in itself. It seems to have an assumption of /static
type checking/. But if one is satisfied with dynamic type checking, then
argument forwarding is quite doable for a virtual function.
To examine it more closely I think we should be clear on what argument
forwarding MEANS in this context.
Is it just forwarding a set of actual arguments, e.g. a virtual function
that gets an argument pack as arguments, and calls an overloaded free
function F with those arguments? And that can be overridden to e.g. also
log this call? That's not so difficult with dynamic type checking, one
can e.g. use a sequence of `std::any` instances.
--------------------------------------------------------------------------
#include <any>
using std::any, std::any_cast;
#include <iostream>
using std::cout, std::endl;
#include <string>
using std::string;
#include <iterator>
using std::begin, std::end;
template< class T > using p_ = T*;
template< class T > using r_ = T&;
class Base
{
public:
// Here nothing is known about types of arguments, or their number, in
// a derived class override.
virtual void foo( p_<const any> first, p_<const any> beyond ) const
= 0;
template< class... Args >
void pass_to_foo( const Args&... args ) const
{
const any arg_list[]{ args... };
foo( begin( arg_list ), end( arg_list ) );
}
};
class Derived:
public Base
{
public:
void foo( p_<const any> first, p_<const any> beyond ) const override
{
for( auto p = first; p != beyond; ++p )
{
if( p->type() == typeid( int ) )
{
cout << "int arg: " << any_cast<int>( *p ) << "." << endl;
}
else if( p->type() == typeid( string ) )
{
cout << "string arg: \"" << any_cast<string>( *p ) <<
"\"." << endl;
}
else
{
throw "Uh oh, an argument of a type not supported in
Derived.";
}
}
}
};
auto main()
-> int
{
using namespace std::literals;
r_<const Base> o = Derived();
o.pass_to_foo( "The answer is "s, 0b0101010 );
}
--------------------------------------------------------------------------
But, do you also or alternatively require that the top-level call's
expression category - lvalue, xvalue or rvalue - for each argument,
should be recreated? More involved & difficult, I'd say. Because even
the standard library's statically type checked forwarding doesn't manage
to do that 100%:
--------------------------------------------------------------------------
#include <iostream>
using std::cout, std::endl;
#include <string>
using std::string;
#include <utility>
using std::forward;
template< class E >
struct Expression_category{ static auto s() -> string { return
"prvalue"; } };
template< class E >
struct Expression_category<E&>{ static auto s() -> string { return
"lvalue"; } };
template< class E >
struct Expression_category<E&&>{ static auto s() -> string { return
"xvalue"; } };
#define EC( e ) Expression_category<decltype(( e ))>::s()
template< class... Args >
void foo( Args&&... args )
{
const string msgs[] = { (args + " as an " + EC( forward<Args>( args
) ) )... };
for( string const& m : msgs )
{
cout << m << endl;
}
}
auto lval( const string& s ) -> const string& { return s; }
auto xval( string&& s ) -> string&& { return move( s ); }
auto prval( string s ) -> string { return move( s ); }
auto main()
-> int
{
string l = "An lvalue expression!";
string x = "An xvalue expression!";
string p = "A prvalue expression!";
cout << EC( lval( l ) ) << endl;
cout << EC( xval( move( x ) ) ) << endl;
cout << EC( prval( p ) ) << endl;
cout << endl;
cout << "Calling foo:" << endl;
foo( lval( l ), xval( move( x ) ), prval( p ) );
}
--------------------------------------------------------------------------
Output with MinGW g++ 7.3:
lvalue
xvalue
prvalue
Calling foo:
An lvalue expression! as an lvalue
An xvalue expression! as an xvalue
A prvalue expression! as an xvalue
... where the last two lines are the same expression category.
Not sure how to deal with that even on its own.
Cheers!,
- Alf