Через этот вопрос задан много лет назад, но я столкнулся с этим недавно, поэтому я просто опубликую его здесь, надеюсь, что это может помочь некоторым людям.
Использование auto
в качестве возвращаемого типа может быть другим решением. Рассмотрим следующий код:
template<typename Derived>
class Base
{
public:
auto f()
{
static_cast<Derived*>(this)->f();
}
};
Если производный класс не предоставляет допустимую перегрузку, эта функция становится рекурсивной, и, поскольку auto
требует окончательного типа возвращаемого значения, его никогда нельзя вывести, поэтому гарантированно будет выдана ошибка компиляции. Например, в MSVC это что-то вроде:
a function that returns 'auto' cannot be used before it is defined
Это заставляет производный класс обеспечивать реализацию, как и чистая виртуальная функция.
Хорошо, что дополнительный код не требуется, и если производный класс также использует auto
в качестве возвращаемого типа, то эта цепочка может продолжаться столько, сколько потребуется. В некоторых случаях это может быть удобно и гибко, как в Base
и LevelTwo
в следующем коде, которые могут возвращать разные типы при вызове одного и того же интерфейса f
. Однако эта цепочка полностью отключает прямое наследование реализации от базового класса, как в LevelThree
:
template<typename Derived = void>
class Base
{
public:
Base() = default;
~Base() = default;
// interface
auto f()
{
return fImpl();
}
protected:
// implementation chain
auto fImpl()
{
if constexpr (std::is_same_v<Derived, void>)
{
return int(1);
}
else
{
static_cast<Derived*>(this)->fImpl();
}
}
};
template<typename Derived = void>
class LevelTwo : public Base<LevelTwo>
{
public:
LevelTwo() = default;
~LevelTwo() = default;
// inherit interface
using Base<LevelTwo>::f;
protected:
// provide overload
auto fImpl()
{
if constexpr (std::is_same_v<Derived, void>)
{
return float(2);
}
else
{
static_cast<Derived*>(this)->fImpl();
}
}
friend Base;
};
template<typename Derived = void>
class LevelThree : public LevelTwo<LevelThree>
{
public:
LevelThree() = default;
~LevelThree() = default;
using LevelTwo<LevelThree>::f;
protected:
// doesn't provide new implementation, compilation error here
using LevelTwo<LevelThree>::fImpl;
friend LevelTwo;
};
В моем случае производный класс, над которым я работаю, также является производным от другого класса, который предоставляет дополнительную информацию, необходимую для определения, следует ли остановиться на текущем классе или перейти к производному классу. Но в других случаях либо разорвите цепочку, используя фактические типы вместо «авто», либо используйте другие приемы. Но в таких ситуациях, возможно, виртуальная функция является лучшим выбором.
person
X. Sun
schedule
29.11.2019
ctype::scan_is
иctype::do_scan_is
. - person user541686   schedule 09.02.2015