วิธีการรวบรวมเวลาเพื่อกำหนดประเภทอาร์กิวเมนต์ที่มีค่าใช้จ่ายน้อยที่สุด


15

ฉันมีแม่แบบที่มีลักษณะเช่นนี้

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

มีเทมเพลต metaprogramming เข้าใจวิธีที่จะหลีกเลี่ยงการใช้ const อ้างอิงในกรณีที่ประเภทอาร์กิวเมนต์เป็นเรื่องเล็กน้อยเช่นบูลหรือถ่าน? ชอบ:

Foo(stl::smarter_argument<T>::type t) : _t(t) {}

1
ฉันจะไม่กังวลเกี่ยวกับมันหากฟังก์ชั่นมีขนาดเล็กคอมไพเลอร์จะอินไลน์และการอ้างอิงจะไม่เกิดขึ้น หากฟังก์ชันมีขนาดใหญ่ค่าใช้จ่ายเล็ก ๆ น้อย ๆ ในการห่อจำนวนเต็มในการอ้างอิงจะไม่มีนัยสำคัญ
Alan Birtles

1
ฉันจะกังวลมากขึ้นเกี่ยวกับการส่งต่อที่สมบูรณ์แบบแล้วหลีกเลี่ยงการอ้างอิงในประเภทข้อมูลขนาดเล็ก ฉันเดาว่าการส่งผ่านโดยการอ้างอิง r-value สามารถปรับให้เหมาะสมกับ pass-by-value ในกรณีส่วนใหญ่
ซุปเปอร์

สิ่งที่ต้องจำไว้ไม่ได้ชี้ให้เห็นในคำตอบ: สิ่งที่คุณกำลังทำอยู่จะเอาชนะคู่มือการลดหย่อนโดยปริยาย คุณควรจำไว้ว่าให้เขียนคู่มือการหักเงินที่ชัดเจนหากคุณสนใจเกี่ยวกับการหักล้างอาร์กิวเมนต์เทมเพลตคลาสที่ใช้งานFooได้
Brian

คำตอบ:


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

จะไม่is_fundamentalทำงานด้วยหรือไม่
Tarek Dakhran

2
สเกลา @TarekDakhran มีพอยน์เตอร์และ enums ซึ่งไม่ใช่พื้นฐานซึ่งควรส่งผ่านโดยค่า IMO
LF

ฉันไม่คุ้นเคยกับไวยากรณ์ class = void นั่นหมายความว่ามันจะเป็นอะไรก็ได้เพราะมันถูกเพิกเฉย?
cppguy

1
= 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

@ Blueue น่าสนใจขอบคุณสำหรับความคิดเห็น และดูstackoverflow.com/a/6751914/2001017ตามตัวอย่างของคุณ: พอยน์เตอร์และพอยน์เตอร์ของฟังก์ชันอาจมีขนาดต่างกัน แม้แต่พอยน์เตอร์ที่แตกต่างกันก็อาจมีขนาดต่างกัน ความคิดคือการได้ขนาด "ปกติ" ของเครื่อง ฉันได้แทนที่ sizeof ที่คลุมเครือ (เป็นโมฆะ *) โดย sizeof (size_t)
Picaud Vincent

1
@Picaud บางทีคุณอาจต้องการใช้<=แทน==บนเครื่องส่วนใหญ่โค้ดปัจจุบันของคุณใช้เป็นcharตัวอย่างโดยอ้างอิงถ้าฉันเห็นว่าถูกต้อง
n314159

2
คุณอาจต้องการตรวจสอบTว่าสามารถคัดลอกได้เล็กน้อย ตัวอย่างเช่นตัวชี้ที่ใช้ร่วมกันมีเพียงสองเท่าของขนาดsize_tบน plattform ของฉันและมันสามารถนำมาใช้กับตัวชี้เพียงตัวเดียวทำให้มันมีขนาดเท่ากัน แต่คุณต้องการใช้ shared_ptr โดย const อ้างอิงไม่ใช่ค่า
n314159

@ n314159 ใช่ว่าจะเป็นการปรับปรุง คุณสบายดีไหมถ้ารวมความคิดของคุณในคำตอบ
Picaud Vincent

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

น่าสนใจ มีประโยชน์ในการใช้ const auto & สำหรับอาร์กิวเมนต์ตัวสร้างหรือไม่
cppguy

@cppguy: ฉันดีใจที่คุณถามคำถามนั้น หากฉันแทนที่อาร์กิวเมนต์ "const auto & t" ด้วย "const T & t" โค้ดจะไม่คอมไพล์ ข้อผิดพลาดจะอ่าน "... การหักที่ไม่ชัดเจนสำหรับอาร์กิวเมนต์ของเทมเพลตของ 'Foo' ... " บางทีคุณสามารถค้นหาสาเหตุ
BlueTune

1
@cppguy: การสนทนาของเราทำให้เกิดคำถามที่ฉันโพสต์ คุณสามารถค้นหาได้ที่นี่
BlueTune

1
แนวคิดเกินความจริงที่นี่และอ่านได้ยากกว่าทางเลือกอื่น
SS Anne

1
@SS Anne: IMHO ที่ใช้แนวคิด C ++ 20 ไม่ถือว่าเป็นเรื่องเกินความจริง มันเป็นเพียงสง่างาม IMHO ทางเลือกที่ฉันเห็นมาแล้วนั้นยากที่จะอ่านเพราะการใช้เทมเพลตที่ซ้อนกัน
BlueTune
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.