模板专业化:Const&vs Const *

时间:2018-07-11 21:43:01

标签: c++ templates

以下代码可以正常工作并打印:

  

你好

     

世界末日

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

但是,如果您将打印功能更改为

  

无效打印(const T&obj)

     

无效打印(const T * obj)

它总是更喜欢const引用风格。

我读了一些有关模板参数推论的内容,但我没有想到。
谁能向我解释一下和/或建议合理的解决方法?

3 个答案:

答案 0 :(得分:4)

使用

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

Demo

答案 1 :(得分:2)

您的问题与模板无关,而与指针语义和函数重载有关。

那是因为在第二种情况下,您要求一个指向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));
}

但是我认为这些变通方法会使您的接口模糊不清,因为仅凭功能签名对对象进行处理并不明显。您的变体不会出现一些问题,迫使您的用户使用只读指针。

答案 2 :(得分:0)

将一些答案汇总在一起。我想我理解为什么现在会发生这种情况,以及为什么将指针升级为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;
}