Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

forward variable number of arguments precisely to a template method

53 views
Skip to first unread message

Pavel

unread,
Sep 2, 2019, 9:41:51 PM9/2/19
to
Trying to write generic code for forwarding arguments via a template
method to methods defined in an interface precisely (in particular, all
references should be forwarded as references and values as values).
Using gcc 4.8.5 and -std=c++11 (cannot change). The best code I could
come up with so far is below.

Taking the below code as an example, I want generated `callMethod' to
have exactly same last 2 parameter types as the interface method it
calls ((I,I) for ma, (const I&, const I&) for mb and (const I&, I) for
mc). The below code generates, according to nm output,

void Caller<IA, 1>::callMethod<void (IA::*)(I, I), I const&, I
const&>(void (IA::*)(I, I), I const&, I const&)
void Caller<IA, 2>::callMethod<void (IA::*)(I const&, I const&), I&,
I&>(void (IA::*)(I const&, I const&), I&, I&)
void Caller<IA, 3>::callMethod<void (IA::*)(I const&, I), I const&, I
const&>(void (IA::*)(I const&, I), I const&, I const&)

commented implementations of callMethod are also failed tries (the last
one passes all Is to callMethod by value, the first two do not compile
at least one of the instantiations).

Any ideas? (gcc 4.8.5 / -std=c++11 is of utmost interest but any working
configuration would help)

TIA,
-Pavel

-----------
#include <iostream>
#include <functional>
using namespace std;

struct I;
ostream& operator<<(ostream&, const I&);
struct I {
int i_;
I(int i): i_(i) {}
I(const I& i): i_(i.i_) {
cout << "I(c&" << i << ")\n";
}
I(I&& i): i_(i.i_) {
cout << "I(&&" << i << ")\n";
}
};
ostream&
operator<<(ostream& os, const I& i) { return os << i.i_; }

struct IA {
virtual void ma(I, I) = 0;
virtual void mb(const I&, const I&) = 0;
virtual void mc(const I&, I) = 0;
};

template <class T, const int> // second parameter is to
// distinguish the prototypesin nm output with certainty
struct Caller {
Caller(T& to): o(to) {}
T& o;
/*
template<class... A>
void callMethod(void (T::*m)(A...), A&&... args) {
(o.*m)(forward<A>(args)...);
}
template<class... A>
void callMethod(void (T::*m)(A...), A... args) {
(o.*m)(args...);
}
template<class M, class... A>
void callMethod(M m, A... args) {
(o.*m)(args...);
}
*/
template<class M, class... A>
void callMethod(M m, A&&... args) {
(o.*m)(forward<A>(args)...);
}
};

struct A: public IA {
void ma(I, I) override
{ cout << "A::ma(I, I)\n"; }
void mb(const I&, const I&) override
{ cout << "A::mb(const I& , const I&)\n"; }
void mc(const I&, I) override
{ cout << "A::mc(const I& , I)\n"; }
};

int
main(int, char*[])
{
A a;
const I i1{1};
const I i2{2};
I i3{3};
I i4{4};
const I i5{5};
const I i6{6};
Caller<IA, 1>(a).callMethod(&IA::ma, i1, i2);
Caller<IA, 2>(a).callMethod(&IA::mb, i3, i4);
Caller<IA, 3>(a).callMethod(&IA::mc, i5, i6);
return 0;
}
-----------

Öö Tiib

unread,
Sep 3, 2019, 3:24:59 AM9/3/19
to
On Tuesday, 3 September 2019 04:41:51 UTC+3, Pavel wrote:
>
> Any ideas? (gcc 4.8.5 / -std=c++11 is of utmost interest but any working
> configuration would help)

What was the question or issue? Did read 3 times over but somehow missed
it. :( Perhaps I need a coffee.

Vir Campestris

unread,
Sep 3, 2019, 4:29:37 PM9/3/19
to
On 03/09/2019 02:41, Pavel wrote:
> Any ideas? (gcc 4.8.5 / -std=c++11 is of utmost interest but any working
> configuration would help)

We're currently taking the pain of upgrading from GCC4.8. The pain of
_not_ upgrading has become too much... you realise GCC 4.8 came out in
2013, and all you've had since then is bug fixes? And not all bugs get
fixed?

Andy

Pavel

unread,
Sep 4, 2019, 10:47:48 AM9/4/19
to
I want Caller<IA, 1>::callMethod to have prototype
void Caller<IA, 1>::callMethod<void (IA::*)(I, I), I, I>(void (IA::*)(I,
I), I, I)

,

Caller<IA, 2>::callMethod to have prototype
void Caller<IA, 2>::callMethod<void (IA::*)(I const&, I const&), I const&,
I const&>(void (IA::*)(I const&, I const&), I const&, I const&)

and

Caller<IA, 3>::callMethod to have prototype
void Caller<IA, 3>::callMethod<void (IA::*)(I const&, I), I const&,
I>(void (IA::*)(I const&, I), I const&, I)

In other words, I want callMethod generated from the callMethod method
template to take exactly same parameter types for its last 2 parameters
as does the interface method to which the callMethod method forwards.

I could not achieve this by any of callMethod template definitions I
tried. E.g. the currently-uncommented in my example definition generates
the methods with the prototypes shown in the original post (1st and 3rd
of which are different from the above).



Pavel

unread,
Sep 4, 2019, 11:08:19 AM9/4/19
to
Yes. In this case I am the wrong tree to ... blame :-) for non-upgrading.

Regardless, I have not yet got a solution for any compiler. If somebody
threw at me a solution that worked on a later compiler but not gcc
4.8.5, it would be a good addition to my list of reasons for the upgrade.

>
> Andy

Manfred

unread,
Sep 5, 2019, 2:21:56 PM9/5/19
to
FWIW gcc 9.1.1 behaves the same way.

Tim Rentsch

unread,
Sep 6, 2019, 6:17:31 AM9/6/19
to
Pavel <pauldont...@removeyourself.dontspam.yahoo> writes:

> Trying to write generic code for forwarding arguments via a
> template method to methods defined in an interface precisely (in
> particular, all references should be forwarded as references and
> values as values). Using gcc 4.8.5 and -std=c++11 (cannot
> change). The best code I could come up with so far is below.
>
> Taking the below code as an example, I want generated `callMethod'
> to have exactly same last 2 parameter types as the interface
> method it calls ((I,I) for ma, (const I&, const I&) for mb and
> (const I&, I) for mc). [...]

I took a pretty long look at this. I think you're up against a
hard problem. Here's the best I came up with (please excuse
some minor changes in names, etc):

template< typename T, const int >
class Caller {
T &worker;
public:
Caller( T &w ) : worker( w ) {}

template< typename ... Stuff > void
invoke( void (T::*pmf)( Stuff ... ), Stuff ... stuff ){
(worker.*pmf)( stuff ... );
}
};

and in main(), instead of

Caller<IA,1>(a).callMethod( &IA::ma, i1, i2 );
Caller<IA,2>(a).callMethod( &IA::mb, i3, i4 );
Caller<IA,3>(a).callMethod( &IA::mc, i5, i6 );

I have

Caller<IA,1>(a).invoke< I , I >( &IA::ma, i1, i2 );
Caller<IA,2>(a).invoke< const I &, const I & >( &IA::mb, i3, i4 );
Caller<IA,3>(a).invoke< const I &, I >( &IA::mc, i5, i6 );

I looked at using forward(), but I don't think it helps you. I
should add though that I'm still pretty much of a novice with
those things.

My sense is that you will be stuck with having to give the
template argument types explicitly rather than having them be
deduced. I have a similar disclaimer about not having any
level of expertise in template type deduction.

Pavel

unread,
Sep 6, 2019, 11:48:51 PM9/6/19
to
Thanks, I thought about it but wanted to avoid it if I could. I think I
will be able to resolve by-reference-vs-by-value issue specifically by
using some pattern on the argument, something like, in your terms,
invoke<some_type_calculation<Stuff>::type...>() and then maybe wrapping
it to a macro? But this of course sucks -- because of a macro and
because it would only take care of only one of transformations that
function template parameter substitution may do to the pack. I would
rather somehow extract the pack of argument types from method precisely
(this is what I am focusing on now, without much success so far). -Pavel


Pavel

unread,
Sep 7, 2019, 10:17:11 AM9/7/19
to
I just realized the problem was solved (not by me but by a high-class
C++ expert who is a colleague of mine, to my luck). The below solution
works with gcc 4.8.5. The key is to apply "Identity" pattern to the
variable parameter type list and provide more information for type
deduction. The prototypes of generated callMethod member functions are
as below:

void Caller<IA, 1>::callMethod<I, I>(void (IA::*)(I, I),
Identity<I>::type, Identity<I>::type)
void Caller<IA, 2>::callMethod<I const&, I const&>(void (IA::*)(I
const&, I const&), Identity<I const&>::type, Identity<I const&>::type)
void Caller<IA, 3>::callMethod<I const&, I>(void (IA::*)(I const&, I),
Identity<I const&>::type, Identity<I>::type)

where Identity<T>::type is actually T so everything is to my satisfaction:

#include <iostream>
#include <functional>
using namespace std;

template<class M>
struct MethodTraits;

template<class T>
struct Identity {
using type = T;
};

struct I;
ostream& operator<<(ostream&, const I&);
struct I {
int i_;
I(int i): i_(i) {}
I(const I& i): i_(i.i_) {
cout << "I(c&" << i << ")\n";
}
I(I&& i): i_(i.i_) {
cout << "I(&&" << i << ")\n";
}
};
ostream&
operator<<(ostream& os, const I& i) { return os << i.i_; }

struct IA {
virtual void ma(I, I) = 0;
virtual void mb(const I&, const I&) = 0;
virtual void mc(const I&, I) = 0;
};

template <class T, const int>
struct Caller {
Caller(T& to): o(to) {}
T& o;
template<class... A>
void callMethod(void (T::*m)(A...), typename Identity<A>::type... args) {
(o.*m)(args...);

Tim Rentsch

unread,
Sep 18, 2019, 11:22:35 AM9/18/19
to
Pavel <pauldont...@removeyourself.dontspam.yahoo> writes:

> I just realized the problem was solved (not by me but by a high-class
> C++ expert who is a colleague of mine, to my luck). The below solution
> works with gcc 4.8.5. The key is to apply "Identity" pattern to the
> variable parameter type list and provide more information for type
> deduction. [...]
>
> [ and the key part is ]
>
> template<class T>
> struct Identity {
> using type = T;
> };
>
> template <class T, const int>
> struct Caller {
> Caller(T& to): o(to) {}
> T& o;
> template<class... A>
> void callMethod(void (T::*m)(A...), typename Identity<A>::type... args) {
> (o.*m)(args...);
> }
> };

Interesting. It took me a while to figure out what's going on
and why this works. At first it looked exactly backwards, but
in retrospect it makes sense. Kudos to your colleague.
0 new messages