Можно ли отказаться от неявного преобразования, разрешив явное преобразование?

#c #type-conversion

#c #преобразование типов

Вопрос:

Предположим, у меня есть простой Duration класс:

 class Duration
{
    int seconds;
public:
    Duration(int t_seconds) : seconds(t_seconds) { }
};

int main()
{
    Duration t(30);
    t = 60;
}
 

И я решаю, что мне не нравится возможность неявного преобразования из int в Duration . Я могу создать конструктор explicit :

 class Duration
{
    int seconds;
public:
    explicit Duration(int t_seconds) : seconds(t_seconds) { }
};

int main()
{
    Duration t(30); // This is fine, conversion is explicit
    t = 60; // Doesn't compile: implicit conversion no longer present for operator=
}
 

Но что, если я не хочу немедленно прерывать весь вызывающий код, который неявно преобразуется в Duration ? Я хотел бы иметь что-то вроде:

 class Duration
{
    int seconds;
public:
    [[deprecated]]
    Duration(int t_seconds) : seconds(t_seconds) { }

    explicit Duration(int t_seconds) : seconds(t_seconds) { }
};

int main()
{
    Duration t(30); // Compiles, no warnings, uses explicit constructor
    t = 60; // Compiles but emits a deprecation warning because it uses implicit conversion
}
 

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

Однако это невозможно, потому что я не могу Duration::Duration(int) перегрузить Duration::Duration(int) .

Есть ли способ добиться чего-то подобного этому эффекту, кроме «Сделайте преобразование явным, примите, что вызывающий код не будет компилироваться, пока вы не внесете соответствующие изменения»?

Комментарии:

1. Было бы приемлемо включить предупреждение компилятора для неявных преобразований? -Wconversion например, в gcc.

2. На самом деле это не выдает предупреждения для этой ситуации. Однако, вероятно, будет достаточно чего-то в этом роде.

3. Вы можете сделать это с помощью условной компиляции, #ifdef РЕФАКТОРИНГ явной длительности (int) #else Duration(int) . Затем скомпилируйте проект за проектом и определите РЕФАКТОРИНГ (командная строка), протестируйте код. После того, как вы выполнили все проекты, удалите ifdef и все определения (или наоборот, начните с определения во всех проектах и удаляйте его один за другим)

Ответ №1:

Вы можете превратиться Duration(int t_seconds) в шаблонную функцию, которая может принимать int и устанавливать ее устаревшей.

 #include<concepts>

class Duration {
  int seconds;
public:
  template<std::same_as<int> T>
  [[deprecated("uses implicit conversion")]]
  Duration(T t_seconds) : Duration(t_seconds) { }
  
  explicit Duration(int t_seconds) : seconds(t_seconds) { }
};
 

Если вы разрешите t = 0.6 , просто измените значение same_as на convertible_to .

ДЕМОНСТРАЦИЯ.

Комментарии:

1. Версия SFINAE не кажется такой уж плохой , как только это дало мне идею попробовать ее. Отличное предложение!

2. Понизьте версию до C 17 и C 11 .

3. @theScore Использует это как ограничение . Это говорит о том, что это std::same_as<T, int> должно быть выполнено T , чтобы быть допустимым аргументом.

4. Обратите внимание, что это решение приведет к нарушению кода следующим образом: Duration minute() { return {60}; } Причина в том, что при инициализации списка копирования разрешение перегрузки выполняется без учета explicit ness, и тогда программа будет неправильно сформирована, если на самом деле будет выбран явный конструктор.

5. Даже просто template<typename = void> [[deprecated]] Duration(int t_seconds) : Duraction(t_seconds) {} работает, поскольку явная инициализация всегда выбирает не шаблонную