ดำเนินการฟังก์ชันภายในเทมเพลตฟังก์ชันเฉพาะสำหรับประเภทที่มีฟังก์ชันกำหนดไว้เท่านั้น


13

ฉันมีเทมเพลตฟังก์ชั่นที่ใช้หลายประเภทเนื่องจากอินพุต มีเพียงหนึ่งประเภทเท่านั้นที่มีgetInt()ฟังก์ชัน ดังนั้นฉันต้องการรหัสเพื่อเรียกใช้ฟังก์ชันเฉพาะสำหรับประเภทนั้น กรุณาแนะนำวิธีแก้ปัญหา ขอบคุณ

#include <type_traits>
#include <typeinfo>

class X {
    public:
    int getInt(){
        return 9;
    }
};

class Y{

};

template<typename T>
void f(T& v){
    // error: 'class Y' has no member named 'getInt'
    // also tried std::is_same<T, X>::value 
    if(typeid(T).name() == typeid(X).name()){
        int i = v.getInt();// I want this to be called for X only
    }
}

int main(){
    Y y;
    f(y);
}

ไม่เกี่ยวข้องกับปัญหาของคุณ แต่type_infoโครงสร้างมีตัวดำเนินการเปรียบเทียบความเท่าเทียมกันดังนั้นtypeid(T) == typeid(X)ควรทำงานเช่นกัน
โปรแกรมเมอร์บางคนเพื่อน

5
การใช้งาน: มีเงื่อนไขif constexpr is_same_v<T,X>
rafix07

การแก้ปัญหานี้จะกลายเป็นความสง่างามมากขึ้นอย่างเป็นทางการในปลายปีนี้ด้วยแนวคิด ไม่เป็นประโยชน์เลยตอนนี้ฉันรู้แล้ว
สวีดิช

มีหลายวิธีในการแก้ปัญหาของคุณ คู่ที่กล่าวถึงข้างต้น คุณสามารถใช้ลักษณะของตัวแปรที่แตกต่างกันเพื่อดูว่าประเภทมีgetIntสมาชิกcallable หรือไม่ stackoverflow.com มีคำถามอยู่สองสามข้อเกี่ยวกับวิธีดูว่าโครงสร้างหรือคลาสมีฟังก์ชันสมาชิกเฉพาะหรือไม่ถ้าคุณค้นหาเพียงเล็กน้อย
โปรแกรมเมอร์บางคนเพื่อน

คำตอบ:


10

หากคุณต้องการเรียกใช้ฟังก์ชันfสำหรับทุกประเภทที่มีสมาชิกฟังก์ชันgetIntไม่ใช่แค่Xคุณสามารถประกาศ 2 โอเวอร์โหลดสำหรับฟังก์ชั่นf:

  1. สำหรับประเภทที่มีgetIntฟังก์ชั่นสมาชิกรวมถึงชั้นเรียนX

  2. สำหรับทุกประเภทอื่น ๆ Yรวมทั้งระดับ

โซลูชัน C ++ 11 / C ++ 17

เมื่อนึกไว้แล้วคุณสามารถทำสิ่งนี้:

#include <iostream>
#include <type_traits>

template <typename, typename = void>
struct has_getInt : std::false_type {};

template <typename T>
struct has_getInt<T, std::void_t<decltype(((T*)nullptr)->getInt())>> : std::is_convertible<decltype(((T*)nullptr)->getInt()), int>
{};

class X {
public:
    int getInt(){
        return 9;
    }
};

class Y {};

template <typename T,
          typename std::enable_if<!has_getInt<T>::value, T>::type* = nullptr>
void f(T& v) {
    // only for Y
    std::cout << "Y" << std::endl;
}

template <typename T,
          typename std::enable_if<has_getInt<T>::value, T>::type* = nullptr>
void f(T& v){
    // only for X
    int i = v.getInt();
    std::cout << "X" << std::endl;
}

int main() {
    X x;
    f(x);

    Y y;
    f(y);
}

ตรวจสอบออกสด

โปรดทราบว่าstd::void_tมีการนำมาใช้ใน C ++ 17 แต่ถ้าคุณถูก จำกัด ไว้ที่ C ++ 11 มันจะง่ายต่อการติดตั้งvoid_tด้วยตัวเอง:

template <typename...>
using void_t = void;

และนี่คือ C ++ 11 รุ่นสด

เรามีอะไรใน C ++ 20

C ++ 20 นำจำนวนมากของสิ่งที่ดีและหนึ่งในนั้นคือแนวคิด เหนือสิ่งที่ใช้ได้สำหรับ C ++ 11 / C ++ 14 / C ++ 17 สามารถลดลงอย่างมากใน C ++ 20:

#include <iostream>
#include <concepts>

template<typename T>
concept HasGetInt = requires (T& v) { { v.getInt() } -> std::convertible_to<int>; };

class X {
public:
    int getInt(){
        return 9;
    }
};

class Y {};

template <typename T>
void f(T& v) {
    // only for Y
    std::cout << "Y" << std::endl;
}

template <HasGetInt T>
void f(T& v){
    // only for X
    int i = v.getInt();
    std::cout << "X" << std::endl;
}

int main() {
    X x;
    f(x);

    Y y;
    f(y);
}

ตรวจสอบออกสด


ก่อนหน้า C ++ 17 การใช้งานของvoid_tสาเหตุทำให้เกิดปัญหากับคอมไพเลอร์เก่าบางอย่าง (ตามที่ลิงก์ชี้ไป)
Jarod42

ไม่จำเป็นอย่างเคร่งครัดที่จะเขียนสองเกินพิกัด (แทนที่ "ต้องการ" ด้วย "สามารถ" จะดีกว่า imho)
idclev 463035818

@ idclev463035818 อัปเดตแล้ว ขอบคุณ
NutCracker

1
@SSAnne อัพเดทแล้ว
NutCracker

1
คำจำกัดความของแนวคิดไม่ถูกต้อง คุณกำลังกำหนดผลลัพธ์ให้กับ int ดังนั้นแนวคิดควรจะเป็นtemplate<typename T> concept HasGetInt = requires (T& v) { {v.getInt()} -> std::convertible_to<int>; };
ฮุย

8

คุณอาจใช้if constexprจาก C ++ 17:

template<typename T>
void f(T& v){
    if constexpr(std::is_same_v<T, X>) { // Or better create trait has_getInt
        int i = v.getInt();// I want this to be called for X only
    }
    // ...
}

ก่อนหน้านี้คุณจะต้องใช้งานเกินพิกัดและ SFINAE หรือจัดส่งแท็ก


if constexprเป็นคุณลักษณะ C ++ 17
Andrey Semashev

อย่างไรก็ตามสิ่งนี้จะใช้ได้กับคลาสX
NutCracker

คำถามได้รับการอัปเดตเป็น C ++ 11 / C ++ 14 เท่านั้น
NutCracker

@NutCracker: ไม่ดีที่จะอัปเดตแท็ก / คำถามและทำให้คำตอบที่มีอยู่นั้นไม่ถูกต้อง ... (แม้ว่าจะมีคำเตือนเกี่ยวกับเรื่องนี้ก็ตาม)
Jarod42

ฉันเพิ่งอัปเดตแท็ก ... ชื่อคำถามถูกอัปเดตโดย OP
NutCracker

7

ทำให้มันง่ายและเกินพิกัด ทำงานตั้งแต่อย่างน้อย C ++ 98 ...

template<typename T>
void f(T& v)
{
    // do whatever
}

void f(X& v)
{
    int result = v.getInt();
}

นี่ก็เพียงพอแล้วหากมีgetIntฟังก์ชั่นประเภทเดียวเท่านั้น หากมีมากขึ้นก็ไม่ใช่เรื่องง่ายอีกต่อไป มีหลายวิธีที่จะทำนี่คือหนึ่ง:

struct PriorityA { };
struct PriorityB : PriorityA { };

template<typename T>
void f_impl(T& t, PriorityA)
{
    // generic version
}

// use expression SFINAE (-> decltype part)
// to enable/disable this overload
template<typename T>
auto f_impl(T& t, PriorityB) -> decltype(t.getInt(), void())
{
    t.getInt();
}

template<typename T>
void f(T& t)
{
    f_impl(t, PriorityB{ } ); // this will select PriorityB overload if it exists in overload set
                              // otherwise PriorityB gets sliced to PriorityA and calls generic version
}

ตัวอย่างสดพร้อมเอาต์พุตการวินิจฉัย


1
ในกรณีนี้จะใช้งานได้เนื่องจากมีโอเวอร์โหลดเพียงหนึ่งตัว (สำหรับX) แต่ถ้ามีประเภทที่คล้ายกันมากกว่านี้กับสมาชิกgetIntในอนาคตนี่ไม่ใช่วิธีปฏิบัติที่ดี คุณอาจต้องการทราบว่า
NutCracker

@NutCracker ทำเช่นนั้น
jrok
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.