使用 = 删除作为接口描述

Using = delete for interface description

本文关键字:接口 描述 删除 使用      更新时间:2023-10-16

我正在尝试为一个自由函数listenTo(SomeAnimal)提供接口描述,该函数应该对满足特定类型要求的类型进行操作(它应该是动物(。函数参数不应将接口继承机制与纯虚拟方法一起使用。

我破解了一个解决方案,其中 free 函数通过基类的 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;
}

C++还没有概念:-(但是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;
}

演示

但是您可以使用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;
}

避免继承复杂性的另一种方法是创建一个类型特征:

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

当且仅当 t.sound() 调用有效时,can_sound<T> 是 true 类型。

我们现在可以说动物是可以发声的东西。

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

您没有要求替代解决方案。相反,您询问了有关您的解决方案的意见。
好吧,这是我的意见,希望它能帮助你。


那是一种微弱的斯芬娜表情。您可以使用以下方法轻松打破它:

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绰绰有余:

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

这样,您还可以从IAnimal中删除无用的sound定义,并且仍然会遇到一个很好的编译错误。


现在,如果您还想删除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; }
};

这在某种程度上是一种通过仅使用语言当前修订版中的功能来检查概念的方法。
请注意,概念会以某种方式在代码中的某个地方帮助做同样的事情,但它们还不是标准的一部分。

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 << "Boomn"; }
};
struct Word : public IAudible {
};
void announce(const IAudible& audible) {
    audible.sound();
}
int main() {
    Explosion e;
    announce(e);
}

演示: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;