3

I have a set functions with different arguments, but all of them have last element the same. So I need to access that last argument in my function. Looking around I found quite some solutions with template specializations (here and here), but this solution came to my mind:

template<typename ...Args> void function( Args&&... args ) { auto &last = std::get<sizeof...( Args ) - 1 >( std::tie( args... ) ); } 

It may look obvious, but it was not so for me in the begining. It seems to be simple and short, but does this method have any hidden overhead versus classic solutions with template specialization and helper functions/classes?

6
  • std::get or std::tuple_element does equivalent things with template specialization. The potential downside is that you instantiate std::tuple<Arg&...> and std::get instead of potentially less template instantiation of provided solution. Commented Aug 24, 2017 at 17:51
  • It depends on how the compile implements sizeof and how std::tuple (std::tie) is implemented. I guess this is more efficient simply because it instantiates less auxiliary types. However, there maybe very inefficient implementations of std::tuple that at the end is the same as other solutions (e.g. std::tuple is implemented recursively). In conclusion, I think is efficient, but the hidden cost is in std::tuple. Commented Aug 24, 2017 at 17:51
  • @FrançoisAndrieux I think you can change your comment to an answer, I would definitely +1 that. Commented Aug 24, 2017 at 18:04
  • @alfC sizeof should provide compile time constant, there should be no overhead, am I wrong? I would worry more about std::tuple and that is the question could be there runtime overhead for that. Commented Aug 24, 2017 at 18:06
  • @Slava, yes, that is what I mean. IF there is a hidden (compile time) cost, it is in the std::tuple. So the answer is really sensitive to how std::tuple is implemented. If it uses recursion it may be similar to other solutions. Your implementation looks easy because you are just exploiting the fact that someone else implemented std::tuple. Commented Aug 24, 2017 at 23:54

2 Answers 2

2

It seems to be simple and short, but does this method have any hidden overhead versus classic solutions with template specialization and helper functions/classes?

It has a drawback that could be annoying in some cases. If your type has reference qualifiers on member methods, you can encounter problems by getting an lvalue reference out of it.

Let's consider the following example:

#include<utility> #include<tuple> struct S { void foo() && {} }; template<typename T> void f(T &&t) { std::forward<T>(t).foo(); } int main() { f(S{}); } 

Everything works fine for we have originally an rvalue reference and by forwarding the forwarding reference we can safely call the foo member method.

Let's consider now your snippet (note, the following code doesn't compile - continue reading):

#include<utility> #include<tuple> struct S { void foo() && {} }; template<typename... T> void g(T&&... t) { auto &last = std::get<sizeof...(T) - 1 >(std::tie(t...)); last.foo(); } int main() { g(S{}); } 

This won't compile for foo cannot be invoked anymore on a variable having type S & because of the reference qualifier.

On the other side, by forwarding and extracting the last parameter somehow you can keep intact its type and you don't have such a problem.
As an example:

template<typename... T> void g(T&&... t) { std::get<sizeof...(T) - 1>(std::forward_as_tuple(std::forward<T>(t)...)).foo(); } 
Sign up to request clarification or add additional context in comments.

6 Comments

Well, you posted the disease, may as well give the man the cure. Change it to auto&& last (more correct for generic code) and do std::forward<std::tuple_element_t<sizeof...(T) - 1, std::tuple<T...>>>(last).foo();
@NirFriedman Yeah. I was working on it by I got a smoke meanwhile. I'll copy-and-paste your comment in the answer as soon as I'm back if it's fine for you.
SImple solution would be std::move( last ).foo(); but if there is another solution for getting last element that does not involve writing long code that could be even better.
@Slava You don't know if the last parameter was originally an lvalue or an rvalue reference, so you could incur in the same problem by using move if foo had been declared as void foo() & and the last parameter was an lvalue reference.
I see, this not a problem for my particular case, as I do know type of the last element, but for generic code this could be an issue. I see latest code added, so this should be used instead?
|
2

It depends on your implementation and compiler flags. Try it at godbolt.org. gcc 7.2 with -O2 seems to be able to fully optimize it when I tried with this :

#include <tuple> template<typename ...Args> auto function(Args&&... args) { return std::get<sizeof...(Args)-1 >(std::tie(args...)); } int main() { volatile auto x = function(1, 1., 1.f); } 

It produced this :

main: movss xmm0, DWORD PTR .LC0[rip] xor eax, eax movss DWORD PTR [rsp-4], xmm0 ret .LC0: .long 1065353216 

example

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.