16

I have a template that looks like this

template <typename T> class Foo
{
public:
    Foo(const T& t) : _t(t) {}
private:
    const T _t;
};

Is there a savvy template metaprogramming way to avoid using a const reference in cases where the argument type is trivial like a bool or char? like:

Foo(stl::smarter_argument<T>::type t) : _t(t) {}
cppguy
  • 3,611
  • 2
  • 21
  • 36
  • 1
    I wouldn't worry about it, if the function is small the compiler will inline it and the reference wont even exist. If the function is large the tiny cost of wrapping an integer into a reference will be insignificant – Alan Birtles Mar 08 '20 at 09:56
  • 1
    I would worry more about perfect forwarding then avoiding references on small data types. I'm guessing that passing by r-value reference can be optimized to pass-by-value in most cases. – super Mar 08 '20 at 10:01
  • Something to keep in mind, not pointed out in the answers: what you are doing will defeat the implicit deduction guides. You should remember to write an explicit deduction guide if you care about class template argument deduction working for `Foo`. – Brian Bi Mar 08 '20 at 22:47

3 Answers3

13

I think the right type trait is is_scalar. This would work as follows:

template<class T, class = void>
struct smarter_argument{
    using type = const T&;
};

template<class T>
struct smarter_argument<T, std::enable_if_t<std::is_scalar_v<T>>> {
    using type = T;
};

Edit:

The above is still a bit old-school, thanks @HolyBlackCat for reminding me of this more terse version:

template<class T>
using smarter_argument_t = std::conditional_t<std::is_scalar_v<T>, T, const T&>;
n314159
  • 4,990
  • 1
  • 5
  • 20
  • 2
    @TarekDakhran scalar includes pointers and enums which aren't fundamental, which should be passed by value IMO. – L. F. Mar 08 '20 at 10:50
  • I'm not familiar with the class = void syntax. Does that mean it can be anything because it's ignored? – cppguy Mar 08 '20 at 16:40
  • 1
    `= void` means it has a default type that is void, so using `smarter_argument` is actually `smarter_argument`. I left off a name for this argument since we don't need it, hence `class = void` without a name. It is important that the `std::enable_if_t` in case that it is enabled must also be void for it to match the default type. – n314159 Mar 08 '20 at 17:06
  • 2
    Can be simplified to `template using smarter_argument = std::conditional_t, T, const T &>;`. – HolyBlackCat Mar 08 '20 at 20:52
  • `std::conditional_t` and `std::enable_if_t` can be simplified substantially with the use of C++20 concepts. [See this post.](https://stackoverflow.com/a/60586802/11539646) – BlueTune Mar 08 '20 at 21:16
  • @cppguy: Note, that `Foo(T t) requires std::is_scalar_v` is also a 1 liner. IMHO my solution is easier to read because no nested templates are needed. – BlueTune Mar 10 '20 at 16:04
  • @BlueTune there is nothing supporting the idea that nested templates are bad or incorrect. I don't think I've ever seen somebody demanding upvotes before. Let it go. – cppguy Mar 10 '20 at 20:48
3

I would suggest to use sizeof(size_t) (or sizeof(ptrdiff_t)) which returns a "typical" size related to your machine with the hope that any variable of this size fits into a register. In that case you can safely pass it by value. Moreover, as suggested by @n314159 (see comments at the end of this post) it is useful to ensure that the variable is also trivialy_copyable.

Here is a C++17 demo:

#include <array>
#include <ccomplex>
#include <iostream>
#include <type_traits>

template <typename T>
struct maybe_ref
{
  using type = std::conditional_t<sizeof(T) <= sizeof(size_t) and
                                  std::is_trivially_copyable_v<T>, T, const T&>;
};

template <typename T>
using maybe_ref_t = typename maybe_ref<T>::type;

template <typename T>
class Foo
{
 public:
  Foo(maybe_ref_t<T> t) : _t(t)
  {
    std::cout << "is reference ? " << std::boolalpha 
              << std::is_reference_v<decltype(t)> << std::endl;
  }

private:
  const T _t;
};

int main()
{
                                                          // with my machine
  Foo<std::array<double, 1>> a{std::array<double, 1>{}};  // <- by value
  Foo<std::array<double, 2>> b{std::array<double, 2>{}};  // <- by ref

  Foo<double>               c{double{}};                // <- by value
  Foo<std::complex<double>> d{std::complex<double>{}};  // <- by ref
}
Picaud Vincent
  • 10,518
  • 5
  • 31
  • 70
  • Note that there is no such thing as "the pointer size of your machine". [Run for example this](https://godbolt.org/z/V32B2E): `struct Foo { void bar(){ }; int i; }; std::cout << sizeof(&Foo::i) << std::endl; //prints 8 std::cout << sizeof(&Foo::bar) << std::endl; //prints 16` – BlueTune Mar 08 '20 at 12:21
  • @BlueTune Interesting, thanks for the comment. Also see https://stackoverflow.com/a/6751914/2001017 as your example shows: pointers and function pointers may have different sizes. Even different pointers may have different sizes. The idea was to get "typical" size of the machine. I have replaced the ambiguous sizeof(void*) by sizeof(size_t) – Picaud Vincent Mar 08 '20 at 12:57
  • 1
    @Picaud maybe you want to use `<=` instead of `==`, on most machines your current code takes a `char` for example by reference if I see that right. – n314159 Mar 08 '20 at 15:43
  • 2
    You may also want to check for `T` being trivially copyable. For example a shared pointer is only twice the size of `size_t` on my plattform and it can be implemented with just one pointer, getting it down to the same size. But you definitely want to take the shared_ptr by const ref and not by value. – n314159 Mar 08 '20 at 16:28
  • @n314159 yes that would be an improvement. Are you okay if include your idea in my answer? – Picaud Vincent Mar 08 '20 at 18:01
  • @BlueTune To be fair, pointers-to-members are not really pointers (`std::is_pointer_v` returns `false` for them). :P – HolyBlackCat Mar 08 '20 at 20:53
  • @Picaud absolutely. – n314159 Mar 08 '20 at 23:06
  • @HolyBlackCat: I can not verify your statement. Please provide an example in code using `std::is_pointer_v`. – BlueTune Mar 08 '20 at 23:30
2

I would make use of the C++20 keyword requires. Just like that:

#include <iostream>

template<typename T>
class Foo
{
public:
    Foo(T t) requires std::is_scalar_v<T>: _t{t} { std::cout << "is scalar" <<std::endl; }
    Foo(const T& t) requires (not std::is_scalar_v<T>): _t{t} { std::cout << "is not scalar" <<std::endl;}
private:
    const T _t;
};

class cls {};

int main() 
{
    Foo{true};
    Foo{'d'};
    Foo{3.14159};
    cls c;
    Foo{c};

    return 0;
}

You can run the code online to see the following output:

is scalar
is scalar
is scalar
is not scalar
BlueTune
  • 1,023
  • 6
  • 18
  • Interesting. Is there a benefit to using const auto& for the constructor argument? – cppguy Mar 08 '20 at 16:22
  • @cppguy: I am glad you ask that question. If I replace the argument "const auto& t" with "const T& t" the code will not compile. The error reads "... ambiguous deduction for template arguments of 'Foo' ...". Maybe you can find out why? – BlueTune Mar 08 '20 at 16:32
  • Is it because it's struggling to infer the template argument when calling the constructor? – cppguy Mar 08 '20 at 16:45
  • @cppguy: This is also what I am thinking. Note: If you replace "T t" by "auto t" and "const auto& t" by "const T& t" the code will compile. By the way if you find my solution interesting why not voting it up? – BlueTune Mar 08 '20 at 16:56
  • 1
    @cppguy: Our discussion resulted in a question I posed. You can find it [here](https://stackoverflow.com/questions/60590608/what-is-the-correct-syntax-for-stdconcept-in-combination-with-a-templated-stru). – BlueTune Mar 08 '20 at 18:28
  • @cppguy: It turned out that the strange syntax is due to a clang bug! – BlueTune Mar 08 '20 at 19:32
  • 1
    Concepts is overkill here and substantially harder to read than the alternative. – S.S. Anne Mar 08 '20 at 22:43
  • 1
    @S.S. Anne: IMHO using C++20 concepts is never an overkill. It is just elegant. IMHO the alternatives I have seen so far are harder to read, because the use of nested templates. – BlueTune Mar 08 '20 at 23:03