4

I have a template function, that accepts a read-only parameter of the templated type. This is a library function for various platforms, from 8-bit (AVR8) to 32 bit (Cortex). How to pass this parameter?

template< typename T >
void f( const T p ){ ... }

template< typename T >
void f( const T & p ){ ... }

By value is (probably) more efficient for parameter types that are smaller than a pointer, by reference is (probably) more efficient for parameter types that are larger than a pointer, and/or expensive to copy.

Is there any standard way to abstract this choice, like

template< typename T >
void f( pass_efficiently< T > p ){ ... }

?

  • I believe on most systems placing value in a register is a single operation, no matter if the length is shorter or equal to the length of the register. – Yksisarvinen Aug 07 '19 at 21:39
  • Personally I wouldn't bother with this. The possible little bit of performance you save is not worth having to duplicate every function based on if it is small or large. That adds a lot more maintenance cost to a project. – NathanOliver Aug 07 '19 at 21:43
  • For one single function I might not bother with this, but it is a fundamental problem that occurs over and over in the libraries I write (sometimes with multiple parameters). I think a reasonable solution is not *that* complicated, so I hoped for someone to have solved this for me. – Wouter van Ooijen Aug 08 '19 at 07:26
  • Don't bother about that. Read-only: pass const T&. The compiler will know how to treat it efficiently. Always keep in mind that compilers are very smart. – mfnx Aug 08 '19 at 09:19

2 Answers2

3

You can use std::is_fundamental to check if the value is a built in type and overload/specialize to pass by value if true.

There are more things to specialize for using the type support library <type_traits> such as is_enum, is_trivially_copyable, is_array and many more. I would recommend checking it out and seeing what works best for you.

Reference link for std::is_fundamental :
https://en.cppreference.com/w/cpp/types/is_fundamental
Reference link for <type_traits>:
https://en.cppreference.com/w/cpp/header/type_traits

Bryan
  • 441
  • 2
  • 8
  • Unfortunately, this returns false for pointer types, which are likely better passed by value than by reference. – Chris Dodd Aug 07 '19 at 21:41
  • You might want to use the size of the type instead. This wont pick up things like an enum or small classes. – NathanOliver Aug 07 '19 at 21:41
  • My idea was indeed to specialize on the size of the type. I think at the user side that boils down to either pass< t >::t (ugly) or the same wrapped in a macro (ugly). I was hoping for someone to have found a better solution. – Wouter van Ooijen Aug 08 '19 at 07:29
  • I think it's not worth it to use std::is_fundamental. Just pass const T&. The compiler will know what to do. – mfnx Aug 08 '19 at 09:18
0

Expanding from Bryan's answer and your additional question, hopefully to make it clear:

You can compose a type trait using Bryan's information and, if you want, the size. For this example, I'm using std::is_trivially_copyable and an implementation-defined maximum size:

template <typename T>
inline constexpr bool is_efficiently_copyable_v =
    std::is_trivially_copyable_v<T> && (sizeof(T) <= magic_size);

Then, you can use this trait to define which function to use with SFINAE (or concepts in C++20), making them mutually exclusive:

template <typename T>
auto f(const T&) -> std::enable_if_t<!is_efficiently_copyable_v<T>>;

template <typename T>
auto f(T) -> std::enable_if_t<is_efficiently_copyable_v<T>>;

(You can also use any other flavor of SFINAE you want)

This way, your function f's interface always receives the parameter in your desired way. Live example: Notice which functions are called in the assembly output.


Just a small side note: GCC generates the same code for const T& and T parameters in all architectures I tested, but Clang doesn't, so beware of that if you just want to go the const reference route. Live example: you may change the parameter type in the main function and between compilers to verify this.

Joel Filho
  • 1,300
  • 1
  • 5
  • 7
  • 1
    Thanks for the suggestion, but it doesn't scale up well to multiple parameters. But it can trivially be converted to fast_copy< T >::t , which could be hidden behind a macro. – Wouter van Ooijen Aug 08 '19 at 15:39
  • "GCC generates the same code for const T& and T parameters" I hope it doesn't do that things that have a cpopy constructor: passing a ref should never invoke the copy constructor! – Wouter van Ooijen Aug 08 '19 at 15:40
  • @WoutervanOoijen Fair point about the scaling. I don't think there's a way to implicitly construct a type that depends on T in the parameter of the function (see std::forward implementation, for example, that requires explicitly naming T), otherwise that would be very easy to do what you need. And, by the code generated by GCC, I mean each function's code. The copy constructor is called before the object is put on the registers/stack. – Joel Filho Aug 08 '19 at 18:10
  • I checked GCC for avr8. It definitely generates worse code for const uint8_t & than for a plain cont uint8_t. – Wouter van Ooijen Jan 26 '22 at 15:13
  • @WoutervanOoijen yes, if you change the version of the code I linked from GCC 5 to GCC 10, you'll notice the function size increases for my example, as well. I believe they changed the ABI from GCC 9 to 10, for how function parameters are passed. – Joel Filho Jan 26 '22 at 21:49