Статическая утиная типизация на C ++

В C ++ есть своего рода утиная типизация для типов, заданных параметрами шаблона. Мы не знаем, какого типа будут DUCK1 и DUCK2, но пока они могут quack(), он будет компилироваться и запускаться:

template <class DUCK1, class DUCK2>
void let_them_quack(DUCK1* donald, DUCK2* daisy){
  donald->quack();
  daisy->quack();
}

Но писать немного неудобно. Когда мне совершенно безразлично, какие именно типы DUCK1 и DUCK2, но я хочу полностью использовать идею утиной печати, тогда я хотел бы иметь что-то немного иное, чем указано выше:

  1. Я бы не хотел писать повторяющийся и бессмысленный список параметров шаблона (только представьте, что произойдет, если будет 7 уток ...)
  2. Я хотел бы сделать это немного более явным, что типы никогда не используются и что важен только интерфейс.
  3. Хотелось бы иметь своего рода аннотацию / проверку интерфейса. Как-то проясните, какой интерфейс ожидается за типом. (Это, однако, немного отличается от утиного набора текста.)

Предлагает ли C ++ какие-либо функции для реализации одной или нескольких из трех идей?

(Я знаю, что виртуальное наследование - это метод выбора в большинстве случаев для реализации таких шаблонов, но здесь вопрос конкретно касается случая статического полиморфизма.)


person Michael    schedule 27.04.2016    source источник
comment
_1 _... прибывает к вам в TS рядом с вами.   -  person Kerrek SB    schedule 27.04.2016
comment
Что ж, это еще не стандарт, но, надеюсь, C ++ 17 позволит нам использовать _1 _ в качестве параметра функции, чтобы упростить эту задачу.   -  person NathanOliver    schedule 27.04.2016
comment
@KerrekSB Этого нет в списке для C ++ 17, верно? Может, увидим в 2020 году. :(   -  person erip    schedule 27.04.2016
comment
@erip: Вы можете спросить своего поставщика, реализуют ли они TS. Я думаю, что GCC и Clang сделают это или скоро сделают.   -  person Kerrek SB    schedule 27.04.2016
comment
@KerrekSB Я имел в виду как часть стандарта. Я пытаюсь писать все больше и больше кода, соответствующего стандартам. :)   -  person erip    schedule 27.04.2016
comment
@NathanOliver Написание auto в нормальной функции является частью концепции TS. Также: концепция TS со временем станет стандартом, и она уже реализована в GCC 6. Так что, если вам не нужен код, совместимый с C ++ 17, вы, вероятно, могли бы его использовать.   -  person Klemens Morgenstern    schedule 27.04.2016
comment
@KlemensMorgenstern Вот почему я сказал, что это еще не стандарт. Я стараюсь не предлагать нестандартные методы, если ОП специально не просит об этом.   -  person NathanOliver    schedule 27.04.2016
comment
@NathanOliver: OP - взрослый человек, поэтому нет причин скрывать нестандартный код ;-) Ответы, содержащие нестандартный код, прекрасны, если вы четко указали нестандартный статус. Как OP вы должны иметь возможность самостоятельно решать, использовать ли вы его или нет.   -  person Michael    schedule 29.04.2016


Ответы (5)


Относительно вопросов 1 и 2: начиная с C ++ 14 вы можете опустить явный шаблон template <typename ... и использовать auto, но только в лямбдах:

auto let_them_quack = [] (auto & donald, auto & daisy){
    donald.quack();
    daisy.quack();
};

(да, предпочитаю ссылки на указатели). GCC позволяет делать это в обычных функциях в качестве расширения.

Что касается вопроса 3, то то, о чем вы говорите, называется концепциями. Они существовали в C ++ давно, но только как документальный термин. Сейчас выполняется Concepts TS, что позволяет вам написать что-то вроде

template<typename T>
concept bool Quackable = requires(T a) {
    a.quack();
};

void let_them_quack (Quackable & donald, Quackable & daisy);

Обратите внимание, что это еще не C ++, а только разрабатывается техническая спецификация. Однако GCC 6.1, похоже, уже поддерживает его. Возможны реализации концепций и ограничений с использованием текущего C ++; вы можете найти его в boost.

person lisyarus    schedule 27.04.2016
comment
AFAIK ваш лучший пример работает только для лямбд, а не для бесплатных функций. - person NathanOliver; 27.04.2016
comment
@NathanOliver Я тоже так думал, но оба ideone с C ++ 14 и мой локальный gcc приняли его. Вроде расширение, исправлю ответ. - person lisyarus; 27.04.2016
comment
Да, это расширение gcc. - person NathanOliver; 27.04.2016
comment
Я думаю, что это пока единственный ответ, который соответствует сути вопроса. Жаль, что эти идеи еще не стандартны ... - person Michael; 27.04.2016
comment
Всегда всегда всегда компилируйте код gcc с -pedantic :) - person SergeyA; 27.04.2016

Я бы не хотел писать повторяющийся и бессмысленный список параметров шаблона (только представьте, что произойдет, если будет 7 уток ...)

Для этого вы можете использовать вариативные шаблоны и сделать что-то вроде следующего:

template<typename DUCK>
void let_them_quack(DUCK &&d) {
  d.quack();
}

template<typename DUCK, typename... Args>
void let_them_quack(DUCK &&d, Args&& ...args) {
  d.quack();
  let_them_quack(std::forward<Args>(args)...);
}

Живая демонстрация

person 101010    schedule 27.04.2016
comment
Почему вы говорите d.quack() дважды? - person Kerrek SB; 27.04.2016
comment
@KerrekSB Извините, не могли бы вы прояснить ситуацию? Вы имеете в виду тривиальный шаг рекурсии? - person 101010; 27.04.2016

# 2 и # 3 вроде как заботятся о том, что код не будет компилироваться и выдает ошибку компиляции, если данные классы не реализуют интерфейс. Вы также можете сделать это формально:

class duck {

public:
   virtual void quack()=0;
};

Затем объявите параметры функции как принимающие указатель на утку. Ваши классы должны быть унаследованы от этого класса, что делает требования для let_them_quack() кристально ясными.

Что касается №1, то об этом могут позаботиться вариативные шаблоны.

void let_them_quack()
{
}

template <typename ...Args>
void let_them_quack(duck* first_duck, Args && ...args) {
  first_duck->quack();
  let_them_quack(std::forward<Args>(args)...);
}
person Sam Varshavchik    schedule 27.04.2016
comment
Я согласен с Керреком и, кроме того, думаю, что решение ничего не проясняет и его не легче читать / писать ... - person Michael; 27.04.2016

Вы сможете сделать его более красивым с помощью концепции (пока не в стандарте, но очень близко):

http://melpon.org/wandbox/permlink/Vjy2U6BPbsTuSK3u

#include <iostream>

template<typename T>concept bool ItQuacks(){
    return requires (T a) {
        { a.quack() } -> void;
    };
}

void let_them_quack2(ItQuacks* donald, ItQuacks* daisy){
  donald->quack();
  daisy->quack();
}

struct DisneyDuck {
    void quack(){ std::cout << "Quack!";}
};

struct RegularDuck {
    void quack(){ std::cout << "Quack2!";}
};

struct Wolf {
    void woof(){ std::cout << "Woof!";}
};

int main() {
    DisneyDuck q1, q2;
    let_them_quack2(&q1, &q2);

    RegularDuck q3, q4;
    let_them_quack2(&q3, &q4);    

    //Wolf w1, w2;
    //let_them_quack2(&w1, &w2);    // ERROR: constraints not satisfied
}

вывод:

 Quack!Quack!Quack2!Quack2!

Как видите, вы сможете: omit writing a template parameter list, ItQuacks довольно явный, поэтому types are never used and that it's only the interface that matters имеет место. Это I'd like to have sort of an interface annotation/check. тоже имеет место, использование концепции также даст вам содержательное сообщение об ошибке.

person marcinj    schedule 27.04.2016
comment
Синтаксис немного изменился с тех пор, как это было написано: в основном было удалено bool и добавлено auto к параметрам функции. - person Davis Herring; 14.06.2020

Нам нужно написать только одну версию функции:

#include <utility>

template<typename... Quackers>
void let_them_quack(Quackers&& ...quackers) {
  using expand = int[];

  void(expand { 0, (std::forward<Quackers>(quackers).quack(), 0)... });
}

struct Duck {
  void quack() {}
};

int main()
{
  Duck a, b, c;
  let_them_quack(a, b, c, Duck());
}
person Richard Hodges    schedule 27.04.2016