Можно ли использовать явное ключевое слово, чтобы предотвратить автоматическое преобразование параметров метода?

Я знаю, что вы можете использовать ключевое слово C++ 'explicit' для конструкторов классов, чтобы предотвратить автоматическое преобразование типа. Можно ли использовать эту же команду, чтобы предотвратить преобразование параметров для метода класса?

У меня есть два члена класса, один из которых принимает логическое значение в качестве параметра, а другой — беззнаковое целое число. Когда я вызвал функцию с целым числом, компилятор преобразовал параметр в логическое значение и вызвал неправильный метод. Я знаю, что в конечном итоге я заменю bool, но пока не хочу нарушать другие подпрограммы, пока разрабатывается эта новая подпрограмма.


person Superpolock    schedule 06.10.2008    source источник
comment
Удивился тому же самому и подумал, что это был бы полезный синтаксис для некоторых бесплатных функций. Обычно я хочу, чтобы ссылка на производный класс неявно давала базовый класс, за исключением случаев, когда может произойти нежелательное разделение, например, с функцией swap(). Обмен (явный Foo& lhs, явный Foo& rhs) был бы утешительным.   -  person Dwayne Robinson    schedule 30.05.2014


Ответы (8)


Нет, вы не можете использовать явное, но вы можете использовать шаблонную функцию, чтобы перехватывать неправильные типы параметров.

В C++11 шаблонную функцию можно объявить как deleted. Вот простой пример:

#include <iostream>

struct Thing {
    void Foo(int value) {
        std::cout << "Foo: value" << std::endl;
    }

    template <typename T>
    void Foo(T value) = delete;
};

Это дает следующее сообщение об ошибке, если вы пытаетесь вызвать Thing::Foo с параметром size_t:

error: use of deleted function
    ‘void Thing::Foo(T) [with T = long unsigned int]’

В коде pre-C++11 это можно сделать с помощью неопределенной закрытой функции.

class ClassThatOnlyTakesBoolsAndUIntsAsArguments
{
public:
  // Assume definitions for these exist elsewhere
  void Method(bool arg1);
  void Method(unsigned int arg1);

  // Below just an example showing how to do the same thing with more arguments
  void MethodWithMoreParms(bool arg1, SomeType& arg2);
  void MethodWithMoreParms(unsigned int arg1, SomeType& arg2);

private:
  // You can leave these undefined
  template<typename T>
  void Method(T arg1);

  // Below just an example showing how to do the same thing with more arguments
  template<typename T>
  void MethodWithMoreParms(T arg1, SomeType& arg2);
};

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

Повторите этот шаблон для каждого метода, который принимает bool или unsigned int. Не предоставляйте реализацию шаблонной версии метода.

Это заставит пользователя всегда явно вызывать версию bool или unsigned int.

Любая попытка вызвать Method с типом, отличным от bool или unsigned int, не будет скомпилирована, потому что член является закрытым, конечно, с учетом стандартных исключений из правил видимости (друг, внутренние вызовы и т. д.). Если что-то, у кого есть доступ, вызывает закрытый метод, вы получите ошибку компоновщика.

person Patrick Johnmeyer    schedule 06.10.2008
comment
Но это отключит ВСЕ автоматические преобразования. - person Lev; 07.10.2008
comment
Да, это отключит ВСЕ автоматические преобразования. Дэн хочет предотвратить преобразование параметров для метода класса, согласно тексту вопроса, и этот метод удовлетворяет этому. Есть способы, используя специализацию шаблона, что мы могли бы «сопоставить» базовые типы с конкретными методами, если это необходимо. - person Patrick Johnmeyer; 07.10.2008
comment
Я понимаю, что это старый ответ. Есть ли способ использовать delete или какие-то другие современные функции C++ для выполнения того же самого без этой private халтуры? Редактировать: Ахах! Оно работает! - person Apollys supports Monica; 20.09.2019
comment
Да, вы можете использовать delete, хотя я бы не назвал использование private хакерским. Просто не последний способ. Это то место, где StackOverflow не всегда справляется с изменениями. Как бы вы отнеслись к включению вашего ответа в мой, @Apollys, с разделами для С++ 11 и до С++ 11? - person Patrick Johnmeyer; 25.09.2019
comment
Конечно, вы бы предпочли сохранить исходный ответ сверху или поместить сверху вариант C++11? - person Apollys supports Monica; 25.09.2019
comment
Сделанный. Я поместил решение C++11 на первое место, потому что в наши дни оно действительно должно быть популярным вариантом. - person Apollys supports Monica; 26.09.2019

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

person Lev    schedule 06.10.2008
comment
Проголосовал; в конце концов, это правильно отвечает на вопрос. - person Patrick Johnmeyer; 07.10.2008

Ниже приведена очень простая оболочка, которую можно использовать для создания сильного typedef:

template <typename V, class D> 
class StrongType
{
public:
  inline explicit StrongType(V const &v)
  : m_v(v)
  {}

  inline operator V () const
  {
    return m_v;
  }

private:
  V m_v; // use V as "inner" type
};

class Tag1;
typedef StrongType<int, Tag1> Tag1Type;


void b1 (Tag1Type);

void b2 (int i)
{
  b1 (Tag1Type (i));
  b1 (i);                // Error
}

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

class WidthTag;
typedef StrongType<int, WidthTag> Width;  
class HeightTag;
typedef StrongType<int, HeightTag> Height;  

void foo (Width width, Height height);

Клиентам 'foo' будет ясно, какой аргумент есть какой.

person Richard Corden    schedule 06.10.2008
comment
Хотя вопрос довольно старый (думал 9 лет): каждый раз, когда я хочу вызвать foo с двумя целочисленными параметрами, я должен написать foo( Height ( int_1 ), Width ( int_2 ) ), который найдет оператор явного преобразования через поиск, зависящий от аргумента / поиск Кенига? - person SebNag; 25.01.2017
comment
@SebTu: № Height и Width должны быть найдены с помощью поиска без ADL. Выражение Height(int_1) — это явное приведение типа, в результате которого вызывается конструктор Height. ADL здесь не применяется. Следовательно, чтобы обратиться к NS::Height или NS::Width, мы должны либо использовать директиву/декларацию использования, либо явно уточнять имена. - person Richard Corden; 02.02.2017

Что-то, что может сработать для вас, — это использовать шаблоны. Ниже показано, как шаблонная функция foo<>() специализирована для bool, unsigned int и int. Функция main() показывает, как разрешаются вызовы. Обратите внимание, что вызовы, в которых используется константа int, не определяющая суффикс типа, разрешаются в foo<int>(), поэтому вы получите сообщение об ошибке при вызове foo( 1), если вы не специализируетесь на int. В этом случае вызывающим объектам, использующим литеральную целочисленную константу, придется использовать суффикс "U", чтобы разрешить вызов (это может быть поведение, которое вы хотите).

В противном случае вам придется специализироваться на int и использовать суффикс "U" или преобразовать его в unsigned int перед передачей в версию unsigned int (или, возможно, сделать утверждение, что значение не является отрицательным, если вы этого хотите).

#include <stdio.h>

template <typename T>
void foo( T);

template <>
void foo<bool>( bool x)
{
    printf( "foo( bool)\n");
}


template <>
void foo<unsigned int>( unsigned int x)
{
    printf( "foo( unsigned int)\n");
}


template <>
void foo<int>( int x)
{
    printf( "foo( int)\n");
}



int main () 
{
    foo( true);
    foo( false);
    foo( static_cast<unsigned int>( 0));
    foo( 0U);
    foo( 1U);
    foo( 2U);
    foo( 0);
    foo( 1);
    foo( 2);
}
person Michael Burr    schedule 06.10.2008
comment
Я тоже думал о том, чтобы сделать это таким образом; если foo‹int› вызывает foo‹unsigned int› для явного сопоставления. Это лучший способ, если он хочет разрешить некоторые преобразования, но не все. - person Patrick Johnmeyer; 07.10.2008
comment
На самом деле, это действительно тот же метод, что и ваш. Когда я быстро отсканировал ваш незадолго до публикации, меня оттолкнули многопараметрические прототипы, и я подумал, что ваша техника делает что-то другое, чего я не совсем понял. Я должен был прочитать более внимательно. - person Michael Burr; 07.10.2008
comment
Ах, версии с несколькими параметрами должны были пояснить, что это применимо не только к методам с одним параметром, и что нужно было шаблонизировать только рассматриваемый аргумент. Возможно, они больше сбивали с толку, чем приносили пользу? Я добавлю комментарий, чтобы прояснить это. - person Patrick Johnmeyer; 07.10.2008
comment
То, что они немного смутили меня, не означает, что они были сбиты с толку. Если это имеет смысл. - person Michael Burr; 07.10.2008

Принятый в настоящее время ответ (с использованием частной шаблонной функции) хорош, но устарел. В C++11 вместо этого мы можем использовать функции deleted:

#include <iostream>

struct Thing {
    void Foo(int value) {
        std::cout << "Foo: value" << std::endl;
    }

    template <typename T>
    void Foo(T value) = delete;
};

int main() {
    Thing t;
    int int_value = 1;
    size_t size_t_value = 2;

    t.Foo(int_value);

    // t.Foo(size_t_value);  // fails with below error
    // error: use of deleted function
    //   ‘void Thing::Foo(T) [with T = long unsigned int]’

    return 0;
}

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

person Apollys supports Monica    schedule 20.09.2019
comment
Мне нравится содержание этого, но он мог бы использовать некоторые уточняющие детали и очистку. Например, требуется поддержка компилятора С++ 11 и ссылка на постоянную ссылку принятого ответа (поскольку принятый ответ может быть изменен!). - person Patrick Johnmeyer; 25.09.2019

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

Я занимался разработкой TDD и не понял, что забыл реализовать соответствующий вызов в фиктивном объекте.

person Superpolock    schedule 06.10.2008
comment
Возможно, вы захотите обновить вопрос, чтобы отразить, что то, что вы указали, было проблемой (выбран неправильный метод), на самом деле не было проблемой. Я пересматривал это для своих собственных целей и понял, что даже мое решение не обязательно касается этого, в зависимости от аргументов. - person Patrick Johnmeyer; 14.04.2009

bool ЯВЛЯЕТСЯ целым числом, которое ограничено либо 0, либо 1. В этом вся концепция return 0; логически это то же самое, что сказать return false (хотя не используйте это в коде).

person WolfmanDragon    schedule 06.10.2008
comment
Я считаю, что bool — это отдельный тип в C++. - person Head Geek; 07.10.2008
comment
Да, это тип, но этот тип может содержать только значения int, равные 0 и 1. На самом деле, если я помню своего инструктора по C++ со времен коллажей, 0 — это ложь, а все остальное — правда. Это внутренняя работа bool, а не то, как его следует применять. - person WolfmanDragon; 08.10.2008
comment
Не быть придурком, но дни коллажа? Хе-хе... заставил меня усмехнуться. - person Patrick Johnmeyer; 20.10.2008
comment
Вы правы, что логические значения могут содержать только значения 0 и 1; преобразовав bool в int, вы получите 0 или 1. 0 является ложным, а все остальное верно в обратном случае; если вы оцениваете int или другое числовое значение как bool. Концепция возврата 0 на самом деле пришла из C, когда не было логического типа. - person Patrick Johnmeyer; 20.10.2008
comment
Я все еще должен быть в одном, я только сегодня поймал свой каламбур. - person WolfmanDragon; 22.10.2008

Вы также можете написать версию int, которая вызывает логическую версию.

person aib    schedule 06.10.2008
comment
Он хочет предотвратить такое поведение. - person Sebastian Mach; 22.11.2011