Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A compile time way to determine the least expensive argument type

Tags:

c++

stl

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) {}
like image 823
cppguy Avatar asked Mar 08 '20 09:03

cppguy


3 Answers

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&>;
like image 81
n314159 Avatar answered Oct 09 '22 16:10

n314159


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
}
like image 33
Picaud Vincent Avatar answered Oct 09 '22 15:10

Picaud Vincent


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
like image 2
BlueTune Avatar answered Oct 09 '22 16:10

BlueTune