ตัวดำเนินการสมาชิกที่ไม่ถูกต้องถูกอนุมานจากตัวดำเนินการส่วนกลางที่ถูกต้อง

ฉันมีคลาส 3x3 Matrix และ 3x1 Vector ฉันมีตัวดำเนินการคูณสองตัว อันหนึ่งสำหรับการคูณเมทริกซ์ด้วยสเกลาร์ อีกอันหนึ่งสำหรับการคูณเมทริกซ์ด้วยวัตถุเวกเตอร์ ตัวดำเนินการคูณเมทริกซ์-สเกลาร์เป็นสมาชิกภายในคลาส Matrix และตัวดำเนินการคูณเมทริกซ์-เวกเตอร์เป็นแบบโกลบอล

#include <initializer_list>
#include <array>

template <class T>
class Matrix
{
    public:
        Matrix(std::initializer_list<T> List);
        Matrix() : Matrix({0,0,0,0,0,0,0,0,0}) {}

        template <class S>                      // THE COMPILER TRIES TO USE
        Matrix<T> operator*(const S & Scalar);  // THIS OPERATOR IN BOTH CASES.

        const T & operator()(size_t i, size_t j) const;

    private:
        static constexpr size_t SIZE = 3;
        static constexpr size_t AREA = SIZE * SIZE;
        std::array<T, AREA> E;
};

template <class T>
Matrix<T>::Matrix(std::initializer_list<T> List)
{
    if (List.size() != AREA) throw("Error!");
    for (size_t i=0; i<AREA; i++)
    {
        E[i] = *(List.begin() + i);
    }
}

template <class T>
const T & Matrix<T>::operator()(size_t i, size_t j) const
{
    return E[SIZE * j + i];
}

template <class T>
template <class S>
Matrix<T> Matrix<T>::operator*(const S & Scalar)
{
    const T ScalarT = static_cast<T>(Scalar);
    Matrix<T> Result;
    for (size_t i=0; i<AREA; i++)
    {
        Result.E[i] = E[i] * ScalarT;
    }
    return Result;
}

template <class T>
class Vector
{
    public:
        Vector(std::initializer_list<T> List);
        Vector() : Vector({0,0,0}) {};
        const T & operator()(size_t i) const;
              T & operator()(size_t i);

    private:
        static constexpr size_t SIZE = 3;
        std::array<T, SIZE> E;
};

template <class T>
Vector<T>::Vector(std::initializer_list<T> List)
{
    if (List.size() != SIZE) throw("Error!");
    for (size_t i=0; i<SIZE; i++)
    {
        E[i] = *(List.begin() + i);
    }
}

template <class T>
const T & Vector<T>::operator()(size_t i) const
{
    return E[i];
}

template <class T>
T & Vector<T>::operator()(size_t i)
{
    return E[i];
}

template <class T>  // THE COMPILER NEVER TRIES USING THIS GLOBAL OPERATOR.
Vector<T> operator*(const Matrix<T> & Mat, const Vector<T> & Vec)
{
    Vector<T> Result;
    Result(0) = Mat(0,0) * Vec(0) + Mat(0,1) * Vec(1) + Mat(0,2) * Vec(2);
    Result(1) = Mat(1,0) * Vec(0) + Mat(1,1) * Vec(1) + Mat(1,2) * Vec(2);
    Result(2) = Mat(2,0) * Vec(0) + Mat(2,1) * Vec(1) + Mat(2,2) * Vec(2);
    return Result;
}

int wmain(int argc, wchar_t *argv[]/*, wchar_t *envp[]*/)
{
    Matrix<float> Mat1({2,  0,  0,
                        0,  2,  0,
                        0,  0,  2});

    Vector<float> Vec1({1,
                        2,
                        3});

    Matrix<float> Mat2 = Mat1 * 2;      // Matrix-Scalar Multiplication
    Vector<float> Vec2 = Mat1 * Vec1;   // Matrix-Vector Multiplication

    return 0;
}

ปัญหาคือ เมื่อฉันพยายามคูณเมทริกซ์-เวกเตอร์ คอมไพลเลอร์จะเลือกและพยายามใช้ตัวดำเนินการคูณเมทริกซ์-สเกลาร์ และทำให้คอมไพเลอร์เกิดข้อผิดพลาด

ถ้าฉันลบตัวดำเนินการคูณเมทริกซ์-สเกลาร์และบรรทัดที่ฉันใช้ โปรแกรมจะทำงานได้สำเร็จ ในทางกลับกัน ถ้าฉันลบตัวดำเนินการคูณเมทริกซ์-เวกเตอร์ มันจะรันได้สำเร็จอีกครั้ง พวกเขาแค่ไม่เข้ากัน และเมื่อมันทำงาน (ไม่ว่าในกรณีใด) การคำนวณทั้งหมดก็จะถูกต้อง

เกิดอะไรขึ้นที่นี่?

คอมไพเลอร์และ IDE: Microsoft Visual Studio 2015 Community Edition


person hkBattousai    schedule 08.03.2016    source แหล่งที่มา
comment
คุณช่วยยกตัวอย่างเล็กๆ น้อยๆ ได้ไหม? มีรายละเอียดที่ไม่จำเป็นมากมาย   -  person TartanLlama    schedule 08.03.2016
comment
@TartanLlama แท้จริงของฉันประกอบด้วยเมทริกซ์และคลาสเวกเตอร์ขนาดใหญ่ ฉันนำเสนอเฉพาะฟังก์ชันที่จำเป็นซึ่งฉันจะใช้ในตัวอย่างขั้นต่ำนี้เท่านั้น นี่เป็นเวอร์ชันที่เรียบง่ายมากอยู่แล้ว ไม่รู้จะไปตัดที่ไหนอีก   -  person hkBattousai    schedule 08.03.2016
comment
@SimonKraemer อา ขอโทษ ฉันแก้ไขมันแล้ว   -  person hkBattousai    schedule 08.03.2016
comment
template<class S> ... (const S&) ตรงกับวัตถุทั้งหมด มันไม่ได้หมายถึงเฉพาะประเภทสเกลาร์หรืออะไรสักอย่าง   -  person M.M    schedule 08.03.2016
comment
การแก้ไขโอเวอร์โหลดจะซับซ้อนมากเมื่อคุณมีโอเปอเรเตอร์เดียวกันทั้งเวอร์ชันสมาชิกและไม่ใช่สมาชิก ฉันขอแนะนำอย่างยิ่งให้ใช้เฉพาะผู้ที่ไม่ใช่สมาชิก operator* (วิธีนี้อาจจะไม่ช่วยแก้ปัญหาของคุณได้ แต่อาจกำจัดปลาเฮอริ่งแดงหรือปัจจัยที่สับสนออกไปได้)   -  person M.M    schedule 08.03.2016
comment
ทำให้มันMatrix<T> operator*(const S & Scalar) const.   -  person n. 1.8e9-where's-my-share m.    schedule 08.03.2016
comment
@M.M หรือเปลี่ยนลำดับของคำจำกัดความประเภทและสร้างฟังก์ชันสมาชิกทั้งสอง หรือเพียงใช้ T แทนพารามิเตอร์เทมเพลตอื่น   -  person Simon Kraemer    schedule 08.03.2016
comment
@hkBattousai มีเหตุผลพิเศษใดที่คุณไม่ยอมรับ T สำหรับผู้ดำเนินการสมาชิกของคุณ?   -  person Simon Kraemer    schedule 08.03.2016
comment
@M.M การสั่งซื้อบางส่วนทำให้ const Vector<T>& เป็นสิ่งที่ต้องการมากกว่า const S& ไม่มีอะไร ซับซ้อน ในตัวดำเนินการที่เป็นสมาชิกเทียบกับตัวดำเนินการที่ไม่เป็นสมาชิก อย่างน้อยที่นี่สมาชิก operator* ควรจะมีคุณสมบัติ const แม้ว่าเสียงดังกราวจะยังคงพบว่าคลุมเครือ   -  person Piotr Skotnicki    schedule 08.03.2016
comment
@SimonKraemer ไม่มีเหตุผลพิเศษ ฉันแค่อยากทำให้มันเป็นแบบทั่วไปที่สุดเท่าที่จะเป็นไปได้   -  person hkBattousai    schedule 08.03.2016
comment
@hkBattousai เปลี่ยนเป็นการยอมรับเท่านั้น T จะช่วยแก้ปัญหาของคุณได้ ควรมีข้อผิดพลาดน้อยลงด้วย ผ่าน เช่น ตัวชี้ไม่สมเหตุสมผล แต่ใช้ได้กับแนวทางปัจจุบันของคุณ แต่ยังดูแลสิ่งต่าง ๆ เช่น const-correctness และสิ่งอื่น ๆ ที่กล่าวถึงในความคิดเห็นด้านบน   -  person Simon Kraemer    schedule 08.03.2016
comment
@PiotrSkotnicki ดูเหมือนเป็นข้อผิดพลาดเสียงดังกราวสำหรับฉัน   -  person n. 1.8e9-where's-my-share m.    schedule 08.03.2016
comment
@TartanLlama คุณพลาด return *this; ในตัวดำเนินการสมาชิก ;-)   -  person Simon Kraemer    schedule 08.03.2016
comment
@n.m. ฉันไม่แน่ใจทั้งหมด ในเวอร์ชันสากล T ถูกใช้สองครั้งสำหรับ Matrix และ Vector ดังนั้นเสียงดังกราวอาจคิดว่าไม่มีใครมีความเชี่ยวชาญมากกว่าอีกอันหนึ่ง ดังนั้นจึงมีความคลุมเครือ Matrix<float> มีความเชี่ยวชาญมากกว่า Matrix<T> เช่นเดียวกับที่ const Vector<T>& มีความเชี่ยวชาญมากกว่า const S&   -  person Piotr Skotnicki    schedule 08.03.2016
comment
@hkBattousai โซลูชันที่ง่ายที่สุดและพกพาได้มากที่สุดคือทำให้สมาชิก operator* เป็นสมาชิกระดับโลก และให้อนุมาน T จาก Matrix<T> ได้ (เช่นเดียวกับใน Vector<T>-version)   -  person Piotr Skotnicki    schedule 08.03.2016
comment
@Piotr ไม่ชัดเจนว่า float ควรเข้าสู่รูปภาพหรือไม่ เรากำลังสั่งซื้อบางส่วน Matrix<float>::operator* หรือ Matrix<T>::operator*   -  person n. 1.8e9-where's-my-share m.    schedule 08.03.2016
comment
@n.m. ฉันเดาว่าเมื่อใช้ Matrix<float> คอมไพเลอร์จะจบลงด้วยการโอเวอร์โหลดสองครั้ง: template<class S> operator*(const Matrix<float>&, const S&) และ template<class T> operator*(const Matrix<T>&, const Vector<T>&) นั่นคือคอมไพเลอร์เห็น float เนื่องจาก Matrix<float> เป็นพารามิเตอร์วัตถุโดยนัย   -  person Piotr Skotnicki    schedule 08.03.2016
comment
@Piotr เห็นได้ชัดว่า gcc และ msvc คิดอย่างอื่น   -  person n. 1.8e9-where's-my-share m.    schedule 08.03.2016
comment
@n.m. ใช่ ฉันรู้ และฉันก็บอกไม่ได้แน่ชัดว่าใครถูก แม้ว่าฉันจะสนับสนุนเสียงดังกราวก็ตาม   -  person Piotr Skotnicki    schedule 08.03.2016
comment
@PiotrSkotnicki clang 3.3 เคยยอมรับโปรแกรมนี้ 3.5 ไม่ยอมรับ แก้ไขข้อบกพร่องหรือข้อบกพร่องใหม่?   -  person n. 1.8e9-where's-my-share m.    schedule 08.03.2016
comment
@n.m. เสียงดังกราวยอมรับรหัสที่คุณวางลิงค์ไป (เลือกโอเปอเรเตอร์สมาชิกซึ่งฉันเห็นด้วย) คุณแน่ใจหรือไม่ว่ามันล้มเหลวด้วย 3.5 ?   -  person Piotr Skotnicki    schedule 08.03.2016
comment
ใช่ เสียงดังกราวยอมรับ gcc ปฏิเสธ และ msvc ยอมรับและเลือกโอเปอเรเตอร์แบบสแตนด์อโลน   -  person n. 1.8e9-where's-my-share m.    schedule 08.03.2016


คำตอบ (1)


ภาษาไม่ได้ระบุว่าเมื่อค้นหาโอเวอร์โหลดเพื่อตรวจสอบขอบเขตที่เป็นไปได้ทั้งหมด ทันทีที่พบผู้สมัครหนึ่งคน ระบบจะใช้เฉพาะโอเวอร์โหลดในขอบเขตนั้นเพื่อเลือกผู้สมัครที่ดีที่สุด หากผู้สมัครรายนั้นไม่สามารถคอมไพล์ด้วยเหตุผลอื่น ก็จะไม่ตรวจสอบขอบเขตเพิ่มเติม

ดังนั้นในกรณีของคุณ เนื่องจากสเกลาร์เป็นพารามิเตอร์เทมเพลต มันจะจับคู่ประเภทใดๆ ก็ตาม ตราบใดที่ตัวถูกดำเนินการทางซ้ายของตัวดำเนินการเป็น Matrix<T> การคูณสเกลาร์จะตรงกับตัวถูกดำเนินการทางขวาใดๆ

คุณสามารถ:

  • เพียงทำให้ทั้งสอง operator* อยู่ในขอบเขตเดียวกันเพื่อให้ทั้งคู่ได้รับการพิจารณาว่ามีการแก้ไขโอเวอร์โหลด
  • เปลี่ยนสเกลาร์ operator เพื่อใช้เช่น Scalar<T> ซึ่งเป็น wrapper แบบบางรอบๆ ประเภทสเกลาร์ แทนที่จะเป็นประเภทเทมเพลตทั่วไป
person Mark B    schedule 15.03.2016