1

I have variadic template class representing a thread with function and input arguments.

template<typename F> class my_thread;

template<typename Return, typename... Input>
class my_thread<Return(Input...)>
{
    template<typename F>
    my_thread(F&& f, Input&&... value) : /* mainly std::forward<Input>... to std::tuple<Input...> */ { }
}

Now, instancing that class is simple for global functions

int fnc(int, double);
my_thread<int(int, double)> thr(fnc, 42, 3.14);

It is (obviously) not that simple for function members of some class.

my_thread<int(int, double)> thr(&Foo::fnc, 42, 3.14); // won't work, unless Foo::fnc is static.

I know, that std::thread, has some mechanics (probably partial specialization), which allows non-static member functions to be passed, if, before all arguments, pointer to instance of that class is passed (std::thread(&Foo::bar, Foo(), 42, 3.14); // OK). I was not able to find out how to make that happen, so my my_thread requires static member functions to be passed, and pointer to instance of that class has to be excplicit argument of that function.

struct Dog
{
    void bark();
    static void bark(Dog* dog)
    {
        dog->bark();
    }
}

That is something I have to live with, but that is not problem.
Problem is instantiating my_thread with that function. I wrote my_thread<int(Foo*, double)> thr(&Foo::bar, this, 3.14); into Visual Studio 2015 and it complained about

error C2664: 'my_thread<int (Foo *, double)>::my_thread(my_thread<int (Foo *, double)> &&)': cannot convert argument 2 from 'Foo *const ' to 'Foo *&&'

I tried some magic with casting, but then, I found out, that passing &*this instead of this works.

I was happy to find out solution (or at least something that compiles and runs on Windows) and I decided to try it on linux with G++. I used same code (using &*this), but G++ was angry at me, because

In member function 'void Foo::SomeFunction()': error: no matching function for call to ‘my_thread<int(Foo*, double)>::my_thread(int (*)(Foo*, double), Foo* const, double&)’
note: candidates are: my_thread(F&&, Input&&...)
note: template argument deduction/substitution failed:
note: cannot convert ‘this’ (type ‘Foo* const’) to type ‘Foo*&&’

I was quite surprised that I got pretty much same error as before. I edited back &*this to just this and I was able to compile and run that on G++ under linux.

My question is:
Who is right? G++ 4.9.2 or MVS2015? How do I solve it, so that my code can be run on both platforms?
Or, am I using bad approach to that? How is std::thread implemented, such that it knows, that when non-static member function is passed, it will require pointer to instance of that class as an argument?


EDIT
I am adding more code, so that my intention will be clearer.

template<typename F> struct my_thread; // std::function like syntax

template<typename Return, typename... Input>
struct my_thread<Return(Input...)>
{
    struct thread_data
    {
        template<typename F>
        thread_data(F&& _func, Input&&... _input) : func(std::forward<F>(_func)), input(std::forward<Input>(_input)...) { }

        std::function<Return(Input...)> func;
        std::tuple<Input...> input;
    };

    template<typename F>
    my_thread(F&& _func, Input&&... _input) : data(std::forward<F>(_func), std::forward<Input>(_input)...) { }

    thread_data data;
};
Zereges
  • 5,139
  • 1
  • 25
  • 49
  • Both are wrong. `this` should work - MSVC bug. `&*this` should also work - GCC bug. Try `+&*this` as a workaround? (There are ways to handle PMFs. Use a trait to recognize pointers to member, and wrap it with `mem_fn`.) – T.C. Sep 04 '15 at 22:09
  • What's wrong with `std::thread` ? – Alexandre C. Sep 04 '15 at 22:12
  • @AlexandreC. nothing (as far as I know), I am not using it, due to learning purposes. – Zereges Sep 04 '15 at 22:19
  • Can you post a [complete example](https://stackoverflow.com/help/mcve)? The error message do not quiet match the sample code you give, e.g. what does class `Foo` look like? – ex-bart Sep 04 '15 at 22:37
  • @ex-bart I could, but I did not think it was neccessary. `Foo` class is some class, which has `static int Foo::bar(Foo*, double)` function – Zereges Sep 04 '15 at 22:41
  • How are `SomeClass` and `Foo` related? I mean you try to pass `this` of type `SomeClass*` to an argument that expects `Foo*` if I interpret the code you provided and the g++ error message correctly. – ex-bart Sep 04 '15 at 22:59

2 Answers2

3

Here:

template<typename F>
my_thread(F&& f, Input&&... value)

you are not deducing "universal references", but rvalue references to Input... parameters (is this what you want?). Here, with Input = Foo*, double, your constructor reads

template<typename F>
my_thread(F&& f, Foo*&& value1, double&& value2)

This explains the Foo*&& part. Now, according to this question, the this pointer is never a lvalue and therefore should have type Foo* (and not Foo* const), and should bind to Foo*&&. This seems to be a MSVC bug.

[Regarding the GCC behavior when using &*this, I don't know.]

Community
  • 1
  • 1
Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • `Input...` is a template parameter pack, why would `Input&&...` not undergo reference-collapsing? – Ben Voigt Sep 04 '15 at 22:25
  • I understand why there is `Foo*&&`. I assume, I used something called perfect forwaring there. Whole constructor is actually this: `template my_thread(F&& f, Input&&... value) : stdfuncion(std::forward(f)), stdtuple(std::forward(value)...) { }` – Zereges Sep 04 '15 at 22:26
  • @BenVoigt: It would, but you have to pass reference types to the `my_thread` template, which may or may not be what the OP intended. Usually those reference types are deduced :) – Alexandre C. Sep 04 '15 at 22:26
  • Also note that the question talks about `&*this`, which is a legal rvalue. Your answer mentions `*&this`, which doesn't exist (`&this` is illegal) – Ben Voigt Sep 04 '15 at 22:26
  • I added whole `my_thread` definition (construct part), so that my intentions are clearer. – Zereges Sep 04 '15 at 22:39
  • And the solution is to make the input parameter types a template argument of the constructor too: `template my_thread(F&& f, Input2&&... value)` (and then use `Input2` when you call `std::forward`). But keep the `Input...` template parameter in the class definition! – ex-bart Sep 04 '15 at 23:09
  • And you'll have to do the same with the `thread_data` constructor, of course. [Live in g++](http://coliru.stacked-crooked.com/a/eebeff323dd447bd). – ex-bart Sep 04 '15 at 23:40
0

Wouldn't it be that the const'ness of the pointer passed in is preventing the compiler from allowing the conversion? In your case, if you assume Foo &&, then you're receiving either a reference to a pointer or a pointer that is a temporary, but in either case, it's a pointer who's value you can actually modify. A pointer that is const in the sense that you have it, cannot change what it is pointing to, e.g., the destination of the pointer itself is stuck. There are two const's when it comes to pointers: one for the pointer itself, and one for what is pointed to. They're written "const" to the left and right of the "" in the variable declaration respectively.

Adam Miller
  • 1,756
  • 1
  • 25
  • 44
  • That would prevent you from creating a non-const lvalue reference, but for an rvalue reference, the compiler is free to create a (non-const) temporary copy of the `const` pointer, and bind to that.... right? – Ben Voigt Sep 04 '15 at 22:24
  • lvalue or rvalue would have nothing to do with whether or not you can chance the object itself I wouldn't think. The whole point of having && is so that the compiler can *eliminate* unnecessary copies of a object. Because in your case you have a generic function where the compiler is inferring the type, you are saying: bind to what I pass this function, if possible binding rvalue, otherwise lvalue. A const pointer has a low instantiation time, but it's also counter intuitive to think about it in the general case because that's not what && was introduced for. – Adam Miller Sep 04 '15 at 22:25