Эквивалент подтипа Ada в С++

Предлагает ли C++ нечто похожее на subtype Ada для сужения типа?

Например:

type Weekday is (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
subtype Working_Day is Weekday range Monday .. Friday;

Ответы

Ответ 1

Нет, не изначально.

То, что вы описываете, может быть лучше всего представлено в виде перечисления с областью действия, сопровождаемого отдельным перечислением с областью действия с подмножеством перечислений, которые разделяют числовые представления с "родительским" перечислением области.

Кроме того, вы можете определить некоторые преобразования между ними, но без размышлений на самом деле невозможно сделать все это изящным и интуитивно понятным, по крайней мере, без жесткого кодирования и дублирования множества вещей, что скорее противоречит цели.

При программировании C++ было бы лучше попытаться полностью отказаться от мышления, пропитанного программированием на других языках.

Тем не менее, это на самом деле довольно хорошая идея, хотя я бы не стал задерживать дыхание!

Обходной путь: просто используйте enum и применяйте проверку диапазона там, где это необходимо.

Ответ 2

То, что вы хотите, может (хотя бы частично) быть реализовано с использованием std::variant представленный в С++ 17.

struct Monday {};
struct Tuesday {};
/* ... etc. */
using WeekDay= std::variant<Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday>;

Следующий код определяет sub_variant_t который создает новый variant из представленного типа. Например, using Working_Day= sub_variant_t<WeekDay,5>; берет первые пять элементов из Weekday.

template<class T,size_t o,class S>
struct sub_variant_h;

template<class T,size_t o,size_t... I>
struct sub_variant_h<T,o,std::index_sequence<I...> >
{
    using type= std::variant<typename std::variant_alternative_t<(I+o),T>... >;
};

template<class T,size_t end, size_t beg=0>
struct sub_variant
{
    using type= typename sub_variant_h<T,beg,std::make_index_sequence<end-beg> >:type;
};

template<class T,size_t end, size_t beg=0>
using sub_variant_t = typename sub_variant<T,end,beg>::type;

Если вы хотите скопировать значения из меньшего типа (Working_Day) в больший (Weekday), вы можете использовать WeekDay d3= var2var<WeekDay>( d1 ); где var2var определяется следующим образом.

template<class toT, class... Types>
toT
var2var( std::variant<Types...> const & v )
{
    return std::visit([](auto&& arg) -> toT {return toT(arg);}, v);
}

Смотрите это livedemo.

Ответ 3

Есть несколько дополнительных различий между перечислениями C++ и перечислениями Ada. Следующий код Ады демонстрирует некоторые из этих различий.

with Ada.Text_IO; use Ada.Text_IO;

procedure Subtype_Example is
   type Days is (Monday, Tueday, Wednesday, Thursday, Friday, Saturday, Sunday);
   subtype Work_Days is Days range Monday..Friday;

begin
   Put_Line("Days of the week:");
   for D in Days'Range loop
      Put_Line(D'Image);
   end loop;
   New_Line;
   Put_Line("Days with classification:");
   for D in Days'Range loop
      Put(D'Image & " is a member of");
      if D in Work_Days then
         Put_Line(" Work_Days");
      else
         Put_Line(" a non-work day");
      end if;
   end loop;

end Subtype_Example;

Выход этой программы:

Days of the week:
MONDAY
TUEDAY
WEDNESDAY
THURSDAY
FRIDAY
SATURDAY
SUNDAY

Days with classification:
MONDAY is a member of Work_Days
TUEDAY is a member of Work_Days
WEDNESDAY is a member of Work_Days
THURSDAY is a member of Work_Days
FRIDAY is a member of Work_Days
SATURDAY is a member of a non-work day
SUNDAY is a member of a non-work day

Подтип Work_Days имеет отношение is-is с типом Days. Каждый участник Work_Days также является участником Days. В этом примере набор допустимых значений для Work_Days является подмножеством набора допустимых значений для Days.

Символы в Аде определяются как перечисление. Поэтому легко определить подтипы типа Character для специальных целей. В следующем примере текст читается из файла и подсчитывается количество вхождений букв верхнего и нижнего регистра, игнорируя все остальные символы в файле.

with Ada.Text_IO; use Ada.Text_IO;

procedure Count_Letters is
   subtype Upper_Case is Character range 'A'..'Z';
   subtype Lower_Case is Character range 'a'..'z';

   Uppers : array(Upper_Case) of Natural;
   Lowers : array(Lower_Case) of Natural;

   File_Name : String(1..1024);
   File_Id   : File_Type;
   Length    : Natural;
   Line      : String(1..100);
begin
   -- set the count arrays to zero
   Uppers := (Others => 0);
   Lowers := (Others => 0);

   Put("Enter the name of the file to read: ");
   Get_Line(Item => File_Name,
            Last => Length);

   -- Open the named file
   Open(File => File_Id,
        Mode => In_File,
        Name => File_Name(1..Length));

   -- Read the file one line at a time
   while not End_Of_File(File_Id) loop
      Get_Line(File => File_Id,
               Item => Line,
               Last => Length);
      -- Count the letters in the line
      for I in 1..Length loop
         if Line(I) in Upper_Case then
            Uppers(Line(I)) := Uppers(Line(I)) + 1;
         elsif Line(I) in Lower_Case then
            Lowers(Line(I)) := Lowers(Line(I)) + 1;
         end if;
      end loop;
   end loop;
   Close(File_Id);

   -- Print the counts of upper case letters
   for Letter in Uppers'Range loop
      Put_Line(Letter'Image & " =>" & Natural'Image(Uppers(Letter)));
   end loop;

   -- print the counts of lower case letters
   for Letter in Lowers'Range loop
      Put_Line(Letter'Image & " =>" & Natural'Image(Lowers(Letter)));
   end loop;
end Count_Letters;

Два подтипа символов определены. Подтип Upper_Case содержит диапазон значений символов от 'A' до 'Z', а подтип Lower_Case содержит диапазон значений символов от 'a' до 'z'.

Для подсчета прочитанных букв созданы два массива. Массив Uppers индексируется набором значений Upper_Case. Каждый элемент массива является экземпляром Natural, который является предопределенным подтипом Integer, содержащим только неотрицательные значения. Массив Lowers индексируется набором значений Lower_Case. Каждый элемент Lowers также является экземпляром Natural.

Программа запрашивает имя файла, открывает этот файл, а затем читает файл по одной строке за раз. Символы в каждой строке анализируются. Если символ является символом Upper_Case, то элемент массива в Uppers, проиндексированный анализируемой буквой, увеличивается. Если символ является символом Lower_Case, элемент массива в Lowers, проиндексированный анализируемой буквой, увеличивается.

Следующий вывод является результатом чтения исходного файла для программы count_letters.

Enter the name of the file to read: count_letters.adb
'A' => 3
'B' => 0
'C' => 12
'D' => 0
'E' => 2
'F' => 13
'G' => 2
'H' => 0
'I' => 21
'J' => 0
'K' => 0
'L' => 36
'M' => 1
'N' => 9
'O' => 7
'P' => 4
'Q' => 0
'R' => 3
'S' => 2
'T' => 3
'U' => 9
'V' => 0
'W' => 0
'X' => 0
'Y' => 0
'Z' => 1
'a' => 51
'b' => 3
'c' => 8
'd' => 19
'e' => 146
'f' => 15
'g' => 16
'h' => 22
'i' => 50
'j' => 0
'k' => 0
'l' => 38
'm' => 13
'n' => 57
'o' => 48
'p' => 35
'q' => 0
'r' => 62
's' => 41
't' => 78
'u' => 19
'v' => 0
'w' => 12
'x' => 2
'y' => 6
'z' => 2

Ответ 4

Я предлагаю использовать шаблоны с static_assert для диапазонов подтипов.

  template<int N, typename T>
  struct XYZ
  {
    static_assert(N >= 0 && N <= 5, "<Explanation of restriction>");
    enum { value = N };
  };

Если вы адаптируете типовое имя T его можно обобщить для произвольных конструкций, т.е. вы предоставляете специализацию с помощью XYZ<int N, WeekDays> {//... }; и один для XYZ<int N, Working_Days> {//... }; ,

Если вы просто хотите перечисления, используйте:

  struct WorkDay {
      enum { Mo, Tu, Mi, Th, Fr };
  };

  struct Day : WorkDay {
      enum { Sa, Su };
  };
  //...
  Day today; //today.Mo, ... today.Su works
  WorkDay tomorrow; //tomorrow.Mo works, but not tomorrow.Su

Ответ 5

Возможно, вы можете перегрузить присвоение постусловием

Ensures(result > 0 && result < 10);  

Чисто теоретический. Не пробовал себя. Но что вы, ребята, думаете?

Но забавно осознавать, что каждое обновление на C++ они продвигают как расширенные функции, все, что программисты Ada считают само собой разумеющимся.

Ответ 6

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

Кроме того, что вы ожидаете, когда кто-то пытается поставить Sunday на Working_Day? Исключение (скорее всего)? Чтобы установить это на Monday? Чтобы установить это на Friday? Сделать объект недействительным? Сохранить то же значение и игнорировать это (плохая идея)?

В качестве примера:

#include <iostream>
#include <string>
using namespace std;

enum class Weekday
{
    Sunday= 0,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
};

template <class T, T min, T max>
class RangedAccess
{
    static_assert(max >= min, "Error min > max");
private:
    T t;

    public:
    RangedAccess(const T& value= min)
    {
        *this= value;
    }


    RangedAccess& operator=(const T& newValue)
    {
        if (newValue > max || newValue < min) {
            throw string("Out of range");
        }
        t= newValue;
    }

    operator const T& () const
    { 
        return t; 
    }

    const T& get() const
    { 
        return t; 
    }
};

using Working_Day= RangedAccess<Weekday, Weekday::Monday, Weekday::Friday>;

int main()
{
    Working_Day workday;

    cout << static_cast<int>(workday.get()) << endl; // Prints 1
    try {
        workday= Weekday::Tuesday;
        cout << static_cast<int>(workday.get()) << endl; // Prints 2
        workday= Weekday::Sunday; // Tries to assign Sunday (0), throws
        cout << static_cast<int>(workday.get()) << endl; // Never gets executed

    } catch (string s) {
        cout << "Exception " << s << endl; // Prints "Exception out of range"
    }
    cout << static_cast<int>(workday.get()) << endl; // Prints 2, as the object remained on Tuesday
}

какие выводы:

1
2
Exception Out of range
2