簡體   English   中英

模板專業化:Const&vs Const *

[英]Template Specialization: Const& versus Const*

以下代碼可以正常工作並打印:

你好

世界末日

class TemplateTester
{
public:

    template<class T>
    void Print(T& obj) { obj.PrintNewline(); }

    template<class T>
    void Print(T* obj) { obj->Print(); /*obj->Test();*/ }
};

class Printer
{
public:
    void PrintNewline() const   { std::cout << m_string.c_str() << "\r\n"; }
    void Print() const          { std::cout << m_string.c_str(); }

    void Test() { m_string = "Oops";  }

    std::string m_string;
};

int main()
{
    Printer print1, print2;
    print1.m_string = "Hello";
    print2.m_string = "World";

    TemplateTester tester;
    tester.Print(print1);
    tester.Print(&print2);

    std::cout << " End ";

    return 0;
}

但是,如果將打印功能更改為

void Print(const T&obj)

無效Print(const T * obj)

它始終喜歡使用const參考風格。

我閱讀了一些有關“模板參數”推論的內容,但是沒有發現任何問題。
誰能向我解釋一下和/或建議合理的解決方法?

template<class T> void Print(const T& obj); // #1
template<class T> void Print(const T* obj); // #2

對於

TemplateTester tester;
tester.Print(print1);  // call #1 with T=Printer (#2 is not viable)
tester.Print(&print2); // call #1 with T=Printer* (#1 is a better match than #2)

正如您在(不平凡的) overload_resolution規則中看到的那樣,對於Printer* ,#1比#2更匹配:

Printer* -> Printer* const & (完全匹配)

Printer* -> const Printer* (轉換)

有幾種解決方法,例如標簽分發:

class TemplateTester
{
private:
    template <typename T>
    void Print(const T& obj, std::false_type) {obj.PrintNewline();}

    template <typename T>
    void Print(const T* obj, std::true_type) {obj->Print(); /*obj->Test();*/}
public:
    template <typename T>
    void Print(T&& obj) {
        Print(obj, std::is_pointer<std::decay_t<T>>{});
    }
};

演示

您的問題與模板無關,而與指針語義和函數重載有關。

那是因為在第二種情況下,您要求一個指向const對象的指針。 這意味着指針將具有只讀權限。 通過傳遞非常量對象的引用,您可以傳遞類似具有讀/寫權限的指針值之類的東西-但是,此類指針沒有函數重載。 如果要使用只讀指針,則必須這樣聲明它們:

const T* ptr = ...
T* const ptr = ...

在將它們傳遞給您的函數之前。

請注意,如果它指向const對象,則需要這樣的指針。 但是可以將這樣的指針重新分配給非const對象(但仍僅具有只讀訪問權限)。 特別是這樣的指針不會將您的對象變成const對象-只是通過該指針保護對象免於更改...

解決方法是引入第三個功能:

template<T>
Print(T* obj){ 
  const T* ptr = obj;
  Print(ptr);
}

如aschepler所建議。 或將指針函數更改為采用任何指針並使用const引用函數:

template<T>
Print(T* obj){
  Print((*obj));
}

但我認為這些解決方案會使您的接口模糊不清,因為僅憑功能簽名對對象進行處理並不明顯。 您的變體不會出現一些問題,迫使您的用戶使用只讀指針。

將一些答案匯總在一起。 我想我理解為什么現在會發生這種情況,以及為什么將指針提升為const指針無法像正常函數調用那樣工作。

如果我正確理解@ Jarod42響應,那是因為模板中的T被評估為Printer*而不是Printer 回想起來,這很有意義。 然后,它將T衰減到作用域塊內的Printer&或Printer *中進行評估,但是在選擇功能簽名后才發生。

這是將所有內容組合在一起的代碼塊。 它包含了一些不同的constness的示例,因為對此早有一些混淆。 感謝大家的貢獻!

#include <iostream>

template <typename T>
void Print(T& obj) { obj.Print(); }

template <typename T>
void Print(T* obj) { obj->Print(); }

template <typename T>
void PrintNewline(const T& obj) { obj.PrintNewline(); }

template <typename T>
void PrintNewline(const T* obj) { obj->PrintNewline(); }

template <typename T>
void BetterPrint(const T& obj, std::false_type) { obj.PrintNewline(); }

template <typename T>
void BetterPrint(const T* obj, std::true_type) { obj->PrintNewline(); }

template <typename T>
void PrintChooser(T&& obj) { BetterPrint(obj, std::is_pointer<std::decay_t<T>>{}); }


class Printer
{
public:
    void Print() const          { std::cout << m_string.c_str(); }
    void PrintNewline() const   { std::cout << m_string.c_str() << "\n"; }

    void Test() { m_string = "Oops";  } // Not const!

    std::string m_string;
};


void Test1(const Printer* val)
{ 
    val->Print();
//    val->Test();  // Object type is const Printer.
    val = nullptr;
}

void Test2(Printer const* val)
{
    val->Print();
//    val->Test();  // Object type is const Printer.
    val = nullptr;
}

void Test3(Printer* const val)
{
    val->Print();
    val->Test();
//    val = nullptr;    // you cannont assign to a value that is const
}


int main()
{
    Printer print1, print2;
    print1.m_string = "Hello";
    print2.m_string = "World";

    Print(print1);  // Print(T&) as expected.
    Print(&print2); // Print(T*) as expected.
    PrintNewline(print1); // PrintNewline(const T&) as expected
//    PrintNewline(&print1); // PrintNewline(const T&) but expected PrintNewline(const T*) - Won't compile

    const Printer* print2Ptr = &print2; // Making user of your API do this is first isn't a happy place.
    PrintNewline(print2Ptr); // PrintNewline(const T*) as expected.

    PrintChooser(print1);
    PrintChooser(&print2);  // Happiness

    Test1(&print1); // Promotes correctly
    Test2(&print1); // Promotes correctly
    Test3(&print1); // Promotes correctly

    Test1(print2Ptr); // Pointer const matches
    Test2(print2Ptr); // Pointer const matches
//    Test3(print2Ptr); // Fails to compile, funciton takes a pointer to a non-cost object.


    return 0;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM