Thread: Perfect forwarding issue - Generics

  1. #1
    Registered User cstryx's Avatar
    Join Date
    Jan 2013
    Location
    Canada
    Posts
    123

    Perfect forwarding issue - Generics

    Say I have something like the following, or very similar to it:
    Code:
    template <typename T1, typename T2>
    auto function(T1 &&t1, T2 &&t2) -> typename std::common_type<T1, T2>::type &&
    {
      if (t1 > t2) return std::forward<T1>(t1);
      return std::forward<T2>(t2);
    }
    The issue I'm having here is that I'm not so sure this is correct for what I want to do. I'd like to end up with a solution that would allow me to modify the variable on the caller's side from the function return (lvalue reference).

    Ex:
    Code:
    function(...) = new_value;
    The code above works (somewhat), but seems bugged. I am certain it has to do with the fact that the common type found might not match T2's type if T1 was found to be the common type, or the reverse. So when I try to do anything with the return, type conflict happens.

    I can see this being an issue if I wanted to have the function being called, act as an lvalue reference and the common type is T1, so the return would be the value of t2, in T1's type, as some kind of reference, which I doubt will work...

    An example of that:
    Code:
    template <typename T1, typename T2>
    auto function(T1 &&t1, T2 &&t2) -> typename std::common_type<T1, T2>::type &&
    {
      if (t1 > t2) return std::forward<T1>(t1);
      return std::forward<T2>(t2);
    }
    Code like this seems to work:
    Code:
    int val1 = 1;
      long val2 = 2;
      auto &&q = function(std::move(val1), std::move(val2));
      ++q;
      std::cout << val1 << std::endl;
      std::cout << val2 << std::endl;
    And modifies val2 to be 3... But if val1 is higher than val2, val1 never gets modified to be 2, and most likely because the common type is long, but trying to modify that value from a reference to type long isn't going to work...?

    I also get a warning about returning a reference to a temp variable, which is confusing because it seems to work as though it's modifying the value from the caller of the function, and not some temp in the function that I wouldn't see change.

    Can anybody help clear the confusion here? :S

    For a function with 1 template type, it seems simple enough:
    Code:
    template <typename T>
    T && f(T &&x, T &&y)
    {
      return x > y ? std::forward<T>(x) : std::forward<T>(y);
    }
    Code:
    f(x, y) = 30;
    Last edited by cstryx; 10-06-2014 at 09:27 PM.

  2. #2
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    Can anybody help clear the confusion here?
    O_o

    The code isn't interesting to me; the idea is flawed so I'll discuss that problem.

    Your speculation is correct; the types `int &&' and `long &&' are not compatible as you are trying to use them.

    Even with C++11/C++14 features, the C++ language requires a variable's type to be known during program execution. You can't change a variable's type during compilation, and you can't change a variable's type during program execution. (You can change the type of an object a pointer or reference references, but you could not change the type of the pointer or reference.) Your idea is, ultimately, an attempt to fix a variable's type during program execution. You can't do that with the C++ language.

    The type returned by your idea, as implemented in C++, is always the same for any two types. You may only ever return one type or the other type, but you are trying to return both. (The condition doesn't change the fact that you have two `return' statements with incompatible types.) The implementation issue could be solved, but you are still trying to resolve the variable's type from information the compiler does not have available. (You could make everything `constexpr' compatible, but I don't think that is what you want.) The type of the result and `q', as attempted, is fixed during compilation by inspecting the types involved not the values the variables store.

    Code:
    int val1;
    long val2;
    std::cin >> val1 >> val2;
    auto && q = some_function(/* ... */);
    The code is illustrative of the underlying problem with your idea. The type of `q' will always be the same--`long &&' for our purposes. Your idea would require that the type of `q' change depending on input provided by a user after the program has been compiled.

    Your case works well in other languages if you'd prefer to write that sort of code.

    [Edit]
    I can't help but think I'm going to regret this edit.

    *sigh*

    The above comments aren't entirely correct.

    You actually can get that behavior with the C++ language using a combination of proxies, expression templates, layers of perfect forwarding, and templates literally everywhere.

    The code would, basically, devolve into a nightmare of proxies conditionally performing every operation on one variable stored within a tuple which would need to be passed around instead of the real types you actually want to return.
    [/Edit]

    [Edit]
    (I'm less worried about this edit.)

    The above monstrosity was offered for the sake of completeness. The technique is used with great success in some libraries, but you should absolutely not attempt to implement such a strategy without an extremely good reason.

    To be honest, I would advise against such a strategy in any event; there are better ways to get clean code.
    [/Edit]

    Soma
    Last edited by phantomotap; 10-06-2014 at 11:00 PM.
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  3. #3
    Registered User cstryx's Avatar
    Join Date
    Jan 2013
    Location
    Canada
    Posts
    123
    Eh, I don't claim for it to be a trivial solution to a unique or unethical problem, I just wanted to see if there was a solution to get something like this to work out of curiosity haha.

    You may only ever return one type or the other type, but you are trying to return both. -- Not 'both' per say, but one or the other as a conditional return. I now believe that perhaps the only way to do this would be to return some kind of wrapper and parse something out of it, making something like this even less than ideal, and less ideal than what I was attempting to do as well.

    The code would, basically, devolve into a nightmare of proxies conditionally performing every operation on one variable stored within a tuple which would need to be passed around instead of the real types you actually want to return. -- Interesting Thanks for the abstract idea. I think it was similar to what I had in mind as I was reading and writing out this reply. I may have written an implementation that works, but again it might not, and I won't know until it's tested either. It definitely is lacking too.

    Thanks for the food for thought. I was previously reading this page: https://groups.google.com/a/isocpp.o...c/XhQGRiy7yywJ
    Last edited by cstryx; 10-06-2014 at 11:08 PM.

  4. #4
    Registered User cstryx's Avatar
    Join Date
    Jan 2013
    Location
    Canada
    Posts
    123
    ** Post ended up being doubled.
    Last edited by cstryx; 10-06-2014 at 11:10 PM.

  5. #5
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    If you just want a function that returns the largest of two variables which can then be assigned, then you would want more of the following:

    Code:
    template <typename T1, typename T2>
    // The function should accept lvalue references - keep in mind that it would be pointless to assign something to a rvalue (temporary). So it makes no sense!
    // The return value should be an lvalue for the same reason.
    auto function(T1& t1, T2& t2) -> std::conditional_t<t1 > t2, T1, T2>
    {
        // Because you are returning an lvalue, there is no need to forward.
        if (t1 > t2) return t1;
        return t2;
    }
    Again, this is flawed as phantom points out because you are deciding the return value at runtime (unless said function is constexpr and the > operator is constexpr and the arguments are constexpr).
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  6. #6
    Registered User cstryx's Avatar
    Join Date
    Jan 2013
    Location
    Canada
    Posts
    123
    Yeah, but if everything is constexpr then that seems like a way to unnecessarily complicate things when you can just return said type directly. Btw, what compiler are you using? I get an error using MinGW. It appears it's not currently available to me using MinGW. I've included the <type_traits> header.

    Code:
    main.cpp:19:43: error: 'conditional_t' in namespace 'std' does not name a type
     auto max(T1& t1, T2& t2) -> typename std::conditional_t<t1 > t2, T1, T2>::type
                                               ^
    main.cpp:19:56: error: expected initializer before '<' token
     auto max(T1& t1, T2& t2) -> typename std::conditional_t<t1 > t2, T1, T2>::type
                                                            ^
    [Finished in 0.4s]
    I'm assuming you're using the Microsoft C++ compiler from Visual Studio, so I'm opening VS 2013 right now to see...

  7. #7
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Yeah, I'm using Visual Studio. Apparently some versions of mingw does not support the *_t prefix type traits (but they're in the standard!). You have to replace them with the non-type versions to compile with mingw:

    typename std::conditional<t1 > t2, T1, T2>::type
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  8. #8
    Registered User cstryx's Avatar
    Join Date
    Jan 2013
    Location
    Canada
    Posts
    123
    I still get errors... Wow that is lame. And before I was complaining about Microsoft's support for certain C++11 keywords...

  9. #9
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    I still get errors... Wow that is lame. And before I was complaining about Microsoft's support for certain C++11 keywords...
    O_o

    1): The `???_t' variant convenience aliases are C++14 standard. You can get C++14 support in "MinGW" builds, but you'll have to tell the compiler you require those features.

    2): In the code Elysia posted, the value `t1 > t2' is not a constant expression. You should get errors using that code. Elysia warned you of that when the code was posted.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  10. #10
    Registered User cstryx's Avatar
    Join Date
    Jan 2013
    Location
    Canada
    Posts
    123
    Quote Originally Posted by phantomotap View Post
    O_o

    1): The `???_t' variant convenience aliases are C++14 standard. You can get C++14 support in "MinGW" builds, but you'll have to tell the compiler you require those features.

    2): In the code Elysia posted, the value `t1 > t2' is not a constant expression. You should get errors using that code. Elysia warned you of that when the code was posted.

    Soma

    1. Not with my build unfortunately.
    Code:
    mingw32-g++: error: unrecognized command line option '-std=c++14'
    GNU extensions don't work either.

    2. I never implied that there shouldn't be errors. If you read, I pointed out the errors with conditional_t in specific; I shouldn't get errors with those as it's a valid identifier. I just wanted to experiment with it, but the issue is not with the code but the compiler. It was actually my mistake that I had omitted a critical part of the above syntax since I didn't just copy and paste it over to my editor.. I'm tired, but the error's should've been obvious if I'd paid attention to them.
    Last edited by cstryx; 10-08-2014 at 12:01 AM.

  11. #11
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    [Edit]
    I never implied that there shouldn't be errors.
    o_O

    Actually, "I still get errors... Wow that is lame" does imply that you think that there shouldn't have been errors with the updated code.
    [/Edit]

    Soma
    Last edited by phantomotap; 10-08-2014 at 01:13 AM.
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  12. #12
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by phantomotap View Post
    1): The `???_t' variant convenience aliases are C++14 standard. You can get C++14 support in "MinGW" builds, but you'll have to tell the compiler you require those features.
    Oh! You're right. Of course. I've just been used to using them I've never actually thought about that they were C++14. I always assumed they were C++11, but come to think of it, I did read that proposal for the C++14 standard. My bad. I'll have to remember that.

    Quote Originally Posted by cstryx View Post
    1. Not with my build unfortunately.
    Code:
    mingw32-g++: error: unrecognized command line option '-std=c++14'
    GNU extensions don't work either.
    Your compiler is too old. Upgrade.

    Anyway, the biggest problem is figuring out the return type. Obviously you decide what type shall be returned at runtime, but you can't do that with templates. You'd have to some something like boost::any (I am not familiar with this, so I could be wrong) to hide the "actual" type. If you don't need to "know" the type--just perform some operations on it, this is pretty easy to achieve with polymorphism (just make a base class with some supported operations and a templated derived class which implements said operations and return a pointer to the base class).
    If you're going to use templates anyway, you have to figure out some way of deducing the type at compile time. Since we can only compare at runtime, that won't work. If both types are integral types, it's pretty easy. Just return the larger type. But issues quickly arise: what if we're using a signed and an unsigned type? What if we're using non-integral types (e.g. some custom classes with overloaded operators)? There's just no good answer. So the best thing to do is just force the issue on the caller. This is what the standard library does with std::max and std::min.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  13. #13
    Registered User cstryx's Avatar
    Join Date
    Jan 2013
    Location
    Canada
    Posts
    123
    Quote Originally Posted by phantomotap View Post
    [Edit]


    o_O

    Actually, "I still get errors... Wow that is lame" does imply that you think that there shouldn't have been errors with the updated code.
    [/Edit]

    Soma
    I think you fail greatly to see that it actually was implying more that I had the idea that the type wasn't supported by MinGW... Why would I think that there shouldn't have been errors? You also fail with reading my reply, as I clearly stated -> "I pointed out the errors with conditional_t in specific; I shouldn't get errors with those as it's a valid identifier." which should hold true if as you say the MinGW compiler will support those identifiers, but I already recognized that my compiler might have been too old to support newer features. You're making conclusions from assumptions, there's no context that says what you claim I thought. So whether you have a crystal ball and can tell me what I 'thought' or not, what are you getting at?

    For what I wrote, it could have, and did, actually mean that I was aware of errors that had occurred, and I was in agreement that the compiler wasn't going to let me have such code compile -- because I had read the documentation and made sure everything was set up appropriately with the compiler I was using..

    Quote Originally Posted by Elysia View Post
    Oh! You're right. Of course. I've just been used to using them I've never actually thought about that they were C++14. I always assumed they were C++11, but come to think of it, I did read that proposal for the C++14 standard. My bad. I'll have to remember that.


    Your compiler is too old. Upgrade.

    Anyway, the biggest problem is figuring out the return type. Obviously you decide what type shall be returned at runtime, but you can't do that with templates. You'd have to some something like boost::any (I am not familiar with this, so I could be wrong) to hide the "actual" type. If you don't need to "know" the type--just perform some operations on it, this is pretty easy to achieve with polymorphism (just make a base class with some supported operations and a templated derived class which implements said operations and return a pointer to the base class).
    If you're going to use templates anyway, you have to figure out some way of deducing the type at compile time. Since we can only compare at runtime, that won't work. If both types are integral types, it's pretty easy. Just return the larger type. But issues quickly arise: what if we're using a signed and an unsigned type? What if we're using non-integral types (e.g. some custom classes with overloaded operators)? There's just no good answer. So the best thing to do is just force the issue on the caller. This is what the standard library does with std::max and std::min.
    I figured. I haven't updated in a while regrettably. I don't use this compiler for any production environment anyways.

    I looked at boost::any a long time ago before writing this thread, I just wondered if there was any new features of the C++ language that would've made some similar implementation a bit less chaotic and complex. I wrote a boost::any-like wrapper, although it had much less overall support for certain operations than the boost library did. It worked by a class which held an object of a generic type, and the wrapper would return and do all necessary conversions to the underlying type.
    Last edited by cstryx; 10-08-2014 at 07:27 PM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Perfect forwarding
    By Elysia in forum C++ Programming
    Replies: 6
    Last Post: 05-10-2013, 12:19 PM
  2. boxing and generics
    By KIBO in forum C# Programming
    Replies: 2
    Last Post: 03-26-2012, 05:35 AM
  3. Native generics?
    By audinue in forum Tech Board
    Replies: 6
    Last Post: 02-04-2009, 10:16 PM
  4. generics question
    By George2 in forum C# Programming
    Replies: 0
    Last Post: 05-11-2008, 02:34 AM
  5. Using 'new' to replace GetType() when using generics
    By MisterT in forum C# Programming
    Replies: 3
    Last Post: 10-14-2006, 03:25 AM