Способ времени компиляции для определения наименее дорогого типа аргумента

15

У меня есть шаблон, который выглядит так

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

Есть ли хитрый способ метапрограммирования шаблонов, чтобы избежать использования константной ссылки в тех случаях, когда тип аргумента тривиален, как bool или char? подобно:

Foo(stl::smarter_argument<T>::type t) : _t(t) {}
cppguy
источник
1
Я бы не волновался об этом, если функция мала, компилятор ее встроит, а ссылка даже не будет существовать. Если функция велика, крошечная стоимость включения целого числа в ссылку будет незначительной
Алан Биртлз,
1
Я бы больше беспокоился об идеальной пересылке, чем об отказе от ссылок на небольшие типы данных. Я предполагаю, что передача по значению r-значения в большинстве случаев может быть оптимизирована для передачи по значению.
супер
Что-то следует иметь в виду, не указав в ответах: то, что вы делаете, победит неявные руководства по выводам. Не забудьте написать явное руководство по выводу, если вам небезразлично, как работает вывод аргументов шаблона класса Foo.
Брайан

Ответы:

13

Я думаю, что правильная черта типа is_scalar. Это будет работать следующим образом:

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;
};

Редактировать:

Выше все еще немного старомодно, спасибо @HolyBlackCat за напоминание мне об этой более краткой версии:

template<class T>
using smarter_argument_t = std::conditional_t<std::is_scalar_v<T>, T, const T&>;
n314159
источник
не будет is_fundamentalработать также?
Тарек Дахран
2
@TarekDakhran скаляр включает в себя указатели и перечисления, которые не являются фундаментальными, которые должны быть переданы по значению IMO.
LF
Я не знаком с синтаксисом class = void. Означает ли это, что это может быть что угодно, потому что это игнорируется?
cppguy
1
= voidозначает, что он имеет тип по умолчанию void, поэтому использование smarter_argument<T>фактически smarter_argument<T, void>. Я оставил имя для этого аргумента, так как оно нам не нужно, следовательно, class = voidбез имени. Важно, чтобы std::enable_if_tв случае, если он включен, он также был недействительным, чтобы соответствовать типу по умолчанию.
n314159
2
Можно упростить до template <typename T> using smarter_argument = std::conditional_t<std::is_scalar_v<T>, T, const T &>;.
HolyBlackCat
3

Я бы предложил использовать sizeof(size_t)(или sizeof(ptrdiff_t)), который возвращает «типичный» размер, связанный с вашей машиной, в надежде, что любая переменная этого размера вписывается в регистр. В этом случае вы можете безопасно передать его по значению. Более того, как предлагает @ n314159 (см. Комментарии в конце этого поста), полезно убедиться, что переменная тоже trivialy_copyable.

Вот демонстрация C ++ 17:

#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
}
Пикауд Винсент
источник
Обратите внимание, что не существует такой вещи, как «размер указателя вашей машины». Запустите, например, это : 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
@BlueTune Интересно, спасибо за комментарий. Также см. Stackoverflow.com/a/6751914/2001017, как показывает ваш пример: указатели и указатели функций могут иметь разные размеры. Даже разные указатели могут иметь разные размеры. Идея заключалась в том, чтобы получить «типичный» размер машины. Я заменил неоднозначный sizeof (void *) на sizeof (size_t)
Пикадо Винсент
1
@Picaud, может быть, вы хотите использовать <=вместо ==, на большинстве машин ваш текущий код принимает, charнапример, по ссылке, если я правильно понимаю.
n314159
2
Вы можете также хотеть проверить, чтобы Tбыть легко копируемым. Например, общий указатель в size_tмоей платформе только вдвое больше размера, и его можно реализовать с помощью всего одного указателя, уменьшив его до того же размера. Но вы определенно хотите взять shared_ptr по const ref, а не по значению.
n314159
@ n314159 да, это было бы улучшением. Вы в порядке, если включите свою идею в мой ответ?
Пикауд Винсент
2

Я бы использовал ключевое слово C ++ 20 requires. Просто так:

#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;
}

Вы можете запустить код онлайн, чтобы увидеть следующий вывод:

is scalar
is scalar
is scalar
is not scalar
BlueTune
источник
Интересно. Есть ли преимущество в использовании const auto & для аргумента конструктора?
cppguy
@cppguy: я рад, что вы задаете этот вопрос. Если я заменю аргумент «const auto & t» на «const T & t», код не скомпилируется. Ошибка гласит «... неоднозначный вывод для аргументов шаблона 'Foo' ...". Может быть, вы можете узнать, почему?
BlueTune
1
@cppguy: наша дискуссия привела к вопросу, который я задал. Вы можете найти это здесь .
BlueTune
1
Понятия здесь излишни и их намного сложнее читать, чем альтернативу.
SS Anne
1
@SS Anne: ИМХО использование концепций C ++ 20 никогда не бывает излишним. Это просто элегантно. ИМХО альтернативы, которые я видел до сих пор, труднее читать, потому что используются вложенные шаблоны.
BlueTune