Неоднозначная функция Вызов базовых классов С++

Я пытаюсь создать Variadic templated класс, который предоставляет метод для каждого класса в списке типов. Ниже приведен пример, который создает метод print для каждого класса в списке типов:

#include <iostream>
#include <string>

// Helper class providing a function call
template <typename T>
class PrintHelper
{
public:
    void print(const T& t) { std::cout << t << std::endl; }
};

// Provides a print method for each type listed
template <typename... Ts>
class Printer : public PrintHelper<Ts>...
{};

int main()
{
    Printer<int, std::string> p;
    p.print(std::string("Hello World")); // Ambiguous Call
}

Прокомментированная строка приводит к ошибке из GCC 4.6.3 в комментариях. Каков правильный способ устранения двусмысленности или я должен смотреть на другой дизайн?

Ответы

Ответ 1

Мне не нравится отвечать на мой собственный вопрос, но я создал следующее решение из комментариев здесь. Он включает все функции print в область видимости и позволяет разрешать перегрузку С++ для всех функций.

#include <iostream>
#include <string>

// Helper class providing a function call
template <typename T>
class PrintHelper
{
public:
    void print(const T& t) { std::cout << t << std::endl; }
};

// Provides a print method for each type listed
template <typename... Ts>
class Printer
{};

template<typename T>
class Printer<T> : public PrintHelper<T>
{
public:
    using PrintHelper<T>::print;
};

template<typename T, typename... Ts>
class Printer<T, Ts...>: public PrintHelper<T>, public Printer<Ts...>
{
public:
    using PrintHelper<T>::print;
    using Printer<Ts...>::print;
};

int main()
{
    Printer<int, std::string> p;
    p.print("Hello World"); // Not an ambiguous Call
}

Ответ 2

Чтобы устранить двусмысленность, можно сделать

template <typename... Ts>
struct Printer : PrintHelper<Ts>...
{
    template <typename U>
    void print (const U& t)
    {
        PrintHelper<U>::print (t);
    }
};

(см. пример)

но это не так здорово, как хотелось бы. В частности, вы не можете распечатать объект, который можно конвертировать в один из типов из списка типов.

При использовании метапрограммирования шаблонов можно отправить на правильный принтер. Для этого вам нужно выбрать тип из Ts..., к которому конвертируется U, и вызвать правый PrintHelper, т.е.

PrintHelper<typename find_convertible<U, Ts...>::type>::print (t);

где find_convertible<U, Ts...> определяется

template <typename U, typename... Ts>
struct find_convertible
{};

template <typename U, typename V, typename... Ts>
struct find_convertible<U, V, Ts...> :
    std::conditional<
        std::is_convertible<U, V>::value, 
        std::common_type<V>, // Aka identity
        find_convertible<U, Ts...>
    >::type
{};

(см. пример)

Ответ 3

Следующий код может решить проблему двусмысленности:

#include <iostream>
#include <string>

// Helper class providing a function call
template <typename T>
class PrintHelper
{
  protected:
    void print_impl(const T& t) { std::cout << t << std::endl; }
};

// Provides a print method for each type listed
template <typename... Ts>
class Printer : public PrintHelper<Ts>...
{
  public:
    template <typename U>
    void print(const U& u) { 
      PrintHelper<U>::print_impl(u); 
    };
};

int main()
{
    Printer<int, std::string> p;
    p.print(std::string("Hello World")); // Ambiguous Call
}

Это не очень приятно из-за требования, чтобы тип U (выводимый по вызову) был точно одним из типов в списке вариационного типа. Возможно, вы сможете немного придумать, чтобы решить эту проблему. С небольшим количеством магии шаблонов и Sfinae вы, вероятно, можете решить это довольно легко (но это, безусловно, не так аккуратно и чисто).

Проблема двусмысленности не связана с использованием шаблонов или вариативных шаблонов, конечно, это прямое применение правил поиска членов (раздел 10.2/2 стандарта), т.е. часто называемый "член правил сокрытия". Если вы возьмете эту более простую версию без шаблонов, вы столкнетесь с такой же проблемой двусмысленности, но с очень простым ее решением:

struct IntPrinter {
  void print(const int& i) { std::cout << i << std::endl; };
};

struct StringPrinter {
  void print(const std::string& s) { std::cout << s << std::endl; };
};

struct IntStringPrinter : IntPrinter, StringPrinter {
  using IntPrinter::print;       // These using-statements will solve the problem
  using StringPrinter::print;    // by importing all 'print' functions to the same 
                                 // overload resolution level.
};

Итак, проблема в том, что проблема заключается в том, что неоднозначность возникает до того, как компилятор даже попытается применить нормальные правила перегрузки, поскольку сначала пытается выяснить, какая ветвь наследования должна следовать, чтобы найти функции-члены, и затем он разрешит перегрузку только на одном уровне наследования. И проблема при использовании вариативных шаблонов заключается в том, что, похоже, нет никакого способа распаковать набор "используемых" операторов для импорта всех функций печати до одного и того же уровня наследования. Если кто-нибудь знает способ распаковать такие заявления-заявления, я все уши. Возможно, вам придется вернуться к решениям с предварительным вариационным шаблоном (например, общий шаблон с 10 аргументами и специализироваться на всех десяти версиях).