2016-09-21 3 views
3

Я пытаюсь предоставить описание интерфейса для бесплатной функции listenTo(SomeAnimal), которая должна работать с типами, которые отвечают определенным требованиям типа (это должно быть животное). Аргументы функции не должны использовать механизм наследования интерфейса с помощью виртуальных методов.Использование = delete для описания интерфейса

Я взломал решение, в котором свободная функция проверяет тип аргумента с помощью инструкции sfinae для базового класса. Чтобы гарантировать, что аргумент реализует интерфейс базового класса, я удалил методы базового класса, используя = delete. Я не нашел подобного решения в Интернете, поэтому я не уверен, имеет ли он смысл, но он работает.

Вот он, любые мнения?

#include <iostream> 
#include <type_traits> 

class IAnimal { 
public: 
    // Interface that needs to be implemented 
    std::string sound() const = delete; 
protected: 
    IAnimal(){} 
}; 


class Cat : public IAnimal { 
public: 
    // Implements deleted method 
    std::string sound() const { 
     return std::string("Meow"); 
    } 

}; 

class WildCat : public Cat { 
public: 
    // Overwrites Cat sound method 
    std::string sound() const { 
     return std::string("Rarr"); 
    } 

}; 

class Dog : public IAnimal{ 
public: 
    // Implements deleted method 
    std::string sound() const { 
     return std::string("Wuff"); 
    } 
}; 


class Car { 
public: 
    // Implements deleted method 
    std::string sound() const { 
     return std::string("Brum"); 
    } 
}; 



// Sfinae tests for proper inheritance 
template<class TAnimal, 
     typename = std::enable_if_t<std::is_base_of<IAnimal, TAnimal>::value> > 
void listenTo(TAnimal const & a) { 
    std::cout << a.sound() << std::endl; 
} 


int main(){ 

    // Objects of type IAnimal can not be instanciated 
    // IAnimal a; 

    // Cats and Dogs behave like IAnimals 
    Cat cat; 
    WildCat wildCat; 
    Dog dog; 
    Car car; 

    listenTo(cat); 
    listenTo(wildCat); 
    listenTo(dog); 

    // A car is no animal -> compile time error 
    // listenTo(car); 

    return 0; 
} 
+2

Вы всегда можете опустить определение (вместо '= удаление;') – Rakete1111

+0

Да, можно, но тогда вы получите не так хороший 'неопределенная ссылка в' ошибка в компиляторе (в случае игнорирования интерфейса). – erikzenker

+1

Я думаю, что в книге Струустапа (C++ Programming Language 2013) он описывает этот подход с аналогичным примером. Мне это хорошо, но я вряд ли способен оценить его. – Aganju

ответ

2

C++ не имеет еще Concepts :-(но GCC-6 реализует его:

template <class T> 
concept bool Animal() { 
    return requires(const T& a) { 
     {a.sound()} -> std::string; 
    }; 
} 

void listenTo(const Animal& animal) { 
    std::cout << animal.sound() << std::endl; 
} 

Demo

Но вы можете создать черты сравнительно легко с is-detected:

typename <typename T> 
using sound_type = decltype(std::declval<const T&>().sound()); 

template <typename T> 
using has_sound = is_detected<sound_type, T>; 

template <typename T> 
using is_animal = has_sound<T>; 
// or std::conditional_t<has_sound<T>::value /*&& other_conditions*/, 
//      std::true_type, std::false_type>; 

А потом регулярные SFINAE:

template<class T> 
std::enable_if_t<is_animal<T>::value> 
listenTo(const T& animal) { 
    std::cout << animal.sound() << std::endl; 
} 
1

Другим путем, избегая осложнения наследования, является созданием типа признака:

#include <iostream> 
#include <type_traits> 

template<class T> 
struct is_animal : std::false_type {}; 

class Cat { 
public: 
    std::string sound() const { 
     return std::string("Meow"); 
    } 
}; 
template<> struct is_animal<Cat> : std::true_type {}; 

class WildCat : public Cat { 
public: 
    // Overwrites Cat sound method 
    std::string sound() const { 
     return std::string("Rarr"); 
    } 

}; 
template<> struct is_animal<WildCat> : std::true_type {}; 

class Dog { 
public: 
    std::string sound() const { 
     return std::string("Wuff"); 
    } 
}; 
template<> struct is_animal<Dog> : std::true_type {}; 


class Car { 
public: 
    std::string sound() const { 
     return std::string("Brum"); 
    } 
}; 



// Sfinae tests for proper inheritance 
template<class TAnimal, 
typename = std::enable_if_t<is_animal<TAnimal>::value> > 
void listenTo(TAnimal const & a) { 
    std::cout << a.sound() << std::endl; 
} 


int main(){ 

    // Objects of type IAnimal can not be instanciated 
    // IAnimal a; 

    // Cats and Dogs behave like IAnimals 
    Cat cat; 
    WildCat wildCat; 
    Dog dog; 
    Car car; 

    listenTo(cat); 
    listenTo(wildCat); 
    listenTo(dog); 

    // A car is no animal -> compile time error 
    // listenTo(car); 

    return 0; 
} 
+0

Благодарим вас за это приятное решение. Есть ли способ описать интерфейс типа TAnimal? – erikzenker

+1

@erikzenker, если вы имеете в виду «есть ли название для этой модели SFNAE, основанной на чертах», я не уверен, каким будет техническое имя. Если вы имеете в виду «что такое TAnimal»? Тогда в этом случае я бы сказал, что это «концепция животного», и черта указывает на то, что класс представляет собой модель этой концепции. Черты и политики, которые мы создаем вокруг TAnimal, описывают его концептуальное поведение, способности и свойства. –

1
namespace details { 
    template<template<class...>class Z, class always_void, class...Ts> 
    struct can_apply:std::false_type{}; 
    template<template<class...>class Z, class...Ts> 
    struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:std::true_type{}; 
} 
template<template<class...>class Z, class...Ts> 
using can_apply=details::can_apply<Z,void,Ts...>; 

Это мета типа черт, которая помогает писать другие черты типа.

template<class T> 
using sound_result = decltype(std::declval<T>().sound()); 

sound_result<T> является результатом t.sound(), где t имеет тип T.

template<class T> 
using can_sound = can_apply<sound_result, T>; 

can_sound<T> истинный тип тогда и только тогда, когда t.sound() справедливо называть.

Теперь мы можем сказать, что животные - это то, что может звучать.

template<bool b> 
using bool_t = std::integral_constant<bool, b>; 

template<class T> 
using is_animal = bool_t< can_sound<T>{} >; // add more requirements 

template<class TAnimal, 
    std::enable_if_t< is_animal<TAnimal const&>{}, int> =0 
> 
void listenTo(TAnimal const & a) { 
    std::cout << a.sound() << std::endl; 
} 

Мы получаем ошибку о том, что нет никакой перегрузки соответствия, если мы пытаемся listenTo(0) или сконвертировано.

Требуется, чтобы .sound() можно было бы написать что-нибудь потоковое.

template<class T> 
using stream_result = decltype(std::declval<std::ostream&>() << std::declval<T>()); 

template<class T> 
using can_stream = can_apply< stream_result, T >; 

template<class T> 
using stream_sound_result = stream_result< sound_result<T> >; 

template<class T> 
using can_stream_sound = can_apply< stream_sound_result, T >; 

Теперь мы можем обновить наше животное тест:

template<class T> 
using is_animal = bool_t< can_stream_sound<T>{} >; 
+0

Ничего себе действительно сложное решение, но я вроде как это. Это очень много шаблонов. Таким образом, есть ли способ сократить его, может быть, библиотекой или макросами, которые вы знаете? – erikzenker

+0

Это испытание для функции функции члена(), но у автомобиля есть это тоже, и вы не можете слушатьTo() автомобиль (определенный в вопросе OP). Вам все еще нужна черта или тег, чтобы указать, что это функция listenTo(). –

+1

@erikzen - половина библиотеки, а именно 'can_apply'. Это дюжина строк. C++ 20 может иметь эквивалентный знак, называемый 'is_detected'. Для написания 'can_x' вам нужна одна строка с использованием шаблона X = decltype'. Это может быть так сложно, как вам нравится, или просто. Это вы определяете «контракт» концепции, как и ваш базовый класс '= delete'.Таким образом, две строки в каждой функции в концепции и строка или так, чтобы приклеить их в одну именованную концепцию (например, is_animal). Вы можете использовать некоторые макросы, чтобы отбросить их на одну строку за каждую функцию, но я бы этого не сделал. – Yakk

1

Вы не просили альтернативного решения. Вместо этого вы попросили мнение о своем решении.
Ну, вот мое мнение, надеясь, что это может вам помочь.


Это слабое выражение sfinae. Вы можете легко разбить его с помощью:

listenTo<Car, void>(car); 

По крайней мере, я хотел бы предложить вам переписать вашу функцию, как это следующим образом:

template<class TAnimal> 
std::enable_if_t<std::is_base_of<IAnimal, TAnimal>::value> 
listenTo(TAnimal const & a) { 
    std::cout << a.sound() << std::endl; 
} 

Это говорит, как он стоит, вы действительно не нужно не использовать ни std::enable_if_t, ни любое другое выражение sfinae.
В этом случае static_assert более чем достаточно:

Таким образом, вы также можете удалить ненужное определение sound от IAnimal и до сих пор у вас будет хорошая ошибка компиляции.


Теперь, если вы хотите отказаться также интерфейс IAnimal, возможное решение (которое не было упомянуто другой ответ) следующим образом:

#include <iostream> 
#include <type_traits> 

template<typename> struct tag {}; 
template<typename... T> struct check; 

template<typename T, typename... U> 
struct check<T, U...>: check<U...> { 
    using check<U...>::verify; 
    static constexpr bool verify(tag<T>) { return true; } 
}; 

template<> 
struct check<> { 
    template<typename T> 
    static constexpr bool verify(tag<T>) { return false; } 
}; 

class Cat { 
public: 
    std::string sound() const { return std::string("Meow"); } 
}; 

class WildCat { 
public: 
    std::string sound() const { return std::string("Rarr"); } 
}; 

class Dog { 
public: 
    std::string sound() const { return std::string("Wuff"); } 
}; 

class Car { 
public: 
    std::string sound() const { return std::string("Brum"); } 
}; 

using AnimalCheck = check<Cat, WildCat, Dog>; 

template<class TAnimal> 
void listenTo(TAnimal const & a) { 
    static_assert(AnimalCheck::verify(tag<TAnimal>{}), "!"); 
    std::cout << a.sound() << std::endl; 
} 

int main(){ 
    Cat cat; 
    WildCat wildCat; 
    Dog dog; 
    Car car; 

    listenTo(cat); 
    listenTo(wildCat); 
    listenTo(dog); 

    // A car is no animal -> compile time error 
    //listenTo(car); 

    return 0; 
} 

В соответствии с просьбой в комментариях , вы можете централизовать проверку существования метода, который будет вызываться в классе check.
В качестве примера:

template<typename T, typename... U> 
struct check<T, U...>: check<U...> { 
    static constexpr auto test() 
    -> decltype(std::declval<T>().sound(), bool{}) 
    { return true; } 

    static_assert(test(), "!"); 

    using check<U...>::verify; 
    static constexpr bool verify(tag<T>) { return true; } 
}; 

Или более компактная версия:

template<typename T, typename... U> 
struct check<T, U...>: check<U...> { 
    static_assert(decltype(std::declval<T>().sound(), std::true_type{}){}, "!"); 

    using check<U...>::verify; 
    static constexpr bool verify(tag<T>) { return true; } 
}; 

Это как-то способ проверки концепции, используя только функции из текущей версии языка.
Обратите внимание, что понятия помогли бы сделать то же самое и где-нибудь в коде, но пока не являются частью стандарта.

+0

Спасибо за ваше решение. У меня есть проблема, что мне нужно взглянуть на определение функции listenTo, чтобы выяснить требования типа TAnimal. У вас есть идея улучшить его в отношении требований? – erikzenker

+0

@erikzenker Я подозреваю, что я не понял вопроса в комментарии. В чем проблема? Вы должны смотреть на 'listenTo' за ... Что? Я не понимаю, извините. – skypjack

+0

Хорошо, я попытаюсь объяснить это несколько иначе. Функция 'listenTo' требует, чтобы тип' TAnimal' предоставлял метод 'sound'. Это требование «скрыто» в теле этой функции. Таким образом, когда я хочу предоставить новый тип для 'listenTo', мне нужно пропустить его уведомление о том, что мне нужно реализовать« звук », а затем реализовать его. Я хотел бы иметь какое-то центральное описание интерфейса, на которое я мог бы смотреть (например, это делается с помощью виртуальных методов). – erikzenker

0

delete Функция, которая удаляет ее, не вносит в нее никакой зависимости. Он говорит, что «этот класс не имеет имеет эту функцию». Что касается реализации/аннотации интерфейса, то это странный способ достижения цели. Это похоже на создание симулятора F-32 с полным кабинетом экипажа и рассказывание очень запутанного первого экспериментального пилота: «Мы удалили все кнопки, чтобы вы знали, что на самом деле существует в реальном самолете».

Путь интерфейсы реализованы в C++ это с виртуальными функциями, и вы аннотировать виртуальную функцию как «чистый» (будет реализован), давая им тело «0», как это:

struct IFace { 
    virtual void sound() = 0; 
}; 

Это делает невозможным создание конкретного экземпляра IFace или любой класс, производный от него, пока вы не достигнете часть иерархии, где sound() реализуется:

struct IAudible { 
    virtual void sound() const = 0; 
}; 

struct Explosion : public IAudible { 
    // note the 'override' keyword, optional but helpful 
    virtual void sound() const override { std::cout << "Boom\n"; } 
}; 

struct Word : public IAudible { 
}; 

void announce(const IAudible& audible) { 
    audible.sound(); 
} 

int main() { 
    Explosion e; 
    announce(e); 
} 

Demo здесь: http://ideone.com/mGnw6o

Но если попытаться создать экземпляр "Word", мы получим ошибку компиляции: http://ideone.com/jriyay

prog.cpp: In function 'int main()': 
prog.cpp:21:14: error: cannot declare variable 'w' to be of abstract type 'Word' 
     Word w; 
prog.cpp:11:12: note: because the following virtual functions are pure within 'Word': 
    struct Word : public IAudible { 
      ^
prog.cpp:4:22: note: virtual void IAudible::sound() const 
     virtual void sound() const = 0; 
+0

Вы правы, что это нечто странное. Я знаю, как чистые виртуальные методы позволяют принудительно реализовать эти методы, но, как я уже говорил, я не хочу использовать чистые виртуальные методы (например, из-за причин производительности). – erikzenker

+0

@erikzenker http://stackoverflow.com/a/449832/257645 Я работал над некоторыми довольно масштабными системами и делал много работы, я все еще жду времени, когда говорю «Ага! этот вызов виртуальной функции! " – kfsone

+0

Ну, если вы не используете аппаратуру середины 90-х и соответствующий компилятор. – kfsone

Смежные вопросы