LINUX.ORG.RU

C++: Вызов метода из шаблона, если он есть

 ,


0

4

Хочу, чтобы шаблонная функция делала какую-то работу и по возможности в определённый момент времени вызывала метод класса. Но если этого метода нет, то ничего страшного.

template<typename T> void f(T &t, int a, int b) {
    std::cout << a << std::endl;
    t.someMethod(a, b); // Если T не имеет someMethod, эта строчка должна быть проигнорирована, но остальные исполняться как раньше
    std::cout << b << std::endl;
}

Я знаю, что есть SFINAE, так что могу написать так:

template<typename T> void f(T &t, int a, int b) {
    std::cout << a << std::endl;
    t.someMethod(a, b);
    std::cout << b << std::endl;
}

template<typename T> void f(T &t, int a, int b) {
    std::cout << a << std::endl;
    std::cout << b << std::endl;
}

То есть две версии шаблона для двух случаев. Если у T нет someMethod, то первый шаблон будет проигнорирован. Проблема в том, что если у T есть someMethod, то получится две функции с одинаковой сигнатурой. А на практике компилятор вообще видит, что я пытаюсь объявить два раза шаблон с одинаковой сигнатурой и ругается сразу, не дожидаясь попытки использовать шаблон с классом имеющим someMethod.

Как можно сделать опциональный вызов метода класса из шаблона?

★★★★★

Последнее исправление: KivApple (всего исправлений: 3)

Как можно сделать опциональный вызов метода класса из шаблона?

Ты Ъ и не можешь на SO прочитать?

anonymous
()
Ответ на: комментарий от anonymous

Я там нашёл вариант с использованием концептов:

template<class T> std::string optionalToString(T* obj)
{
    constexpr bool has_toString = requires(const T& t) {
        t.toString();
    };

    if constexpr (has_toString)
        return obj->toString();
    else
        return "toString not defined";
}

Однако он отказывается компилироваться:

error: expected primary-expression before 'const'
     constexpr bool has_toString = requires(const T& t) {
                                            ^~~~~
KivApple ★★★★★
() автор топика

Сделай SFINAE через enable_if, тогда сможешь объяснить компилятору, какую версию функции генерировать для типа.

filosofia
()
Ответ на: комментарий от KivApple

if constexpr (has_toString)

К чему применяется has_toString? Может туда надо передать obj, в котором он проверит наличие метода? Просто предположение, я ещё с requires не разбирался.

ox55ff ★★★★★
()

А можно без концептов, только с if constexpr

https://gcc.godbolt.org/z/E3jj7dTGf

Хотя if constexpr C++17 фича, во всех компиляторах (clang, gcc, MSVC) она реализована для любого стандарта, так как сами разработчики стандартной библиотеки используют её для реализации стандартной библиотеки, и отрефакторили бывшие ранее SFINAE на if constexpr.

fsb4000 ★★★★★
()

Если метода нет, его можно добавить через шаблон и прочитать приватные данные :) «не забудьте злой смех» (с) Херб Саттер

slackwarrior ★★★★★
()
Ответ на: комментарий от fsb4000

Да, отлично работает. Только возникает вопрос, что делать, если интересующий меня метод принимает аргументы. Пока сделал так (допустим, someMethod принимает два аргумента - int& и int):

template<typename T> static constexpr auto hasSomeMethod(
	T*,
	int *a,
	int b
) -> decltype(std::declval<T>().someMethod(*a, b), true) {
	return true;
}
template<typename T> static constexpr auto hasSomeMethod(...) {
	return false;
}
...
if constexpr (hasSomeMethod<T>(
	(T*) 0,
	(int*) 0,
	0
)) {
	t.someMethod(a, b);
}

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

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от KivApple

Шаблоны и наследование ортогональны :) у Саттера была статья про почему доступ в плюсах носит не обязывающий, а рекомендательный характер, только одни способы через каст к структурам «болимение похожим по лейауту» для любителей грубой силы, а другие — через подходящую сигнатуру шаблона — для «адвокатов языка».

slackwarrior ★★★★★
()
Ответ на: комментарий от slackwarrior

Каст к структуре поможет, безусловно, хотя и является UB (ведь если есть не-public поля, то layout считается implementation-defined, но на популярных реализациях вполне предсказуем). А про шаблоны в первый раз слышу.

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от KivApple

http://www.gotw.ca/gotw/076.htm тут вроде все «грязныи хаки», ду нот репит ит ат ворк :) обычно если тебе это понадобилось, значит нужно более лучше обдумать интерфейс класса в который ломишься. Если он не твой, например, тебе доступен только opaque pointer... но ты знаешь реализацию — годится для пруф-концептов :) но потом лучше поговорить с автором чтоб добавил двери, не вынуждая лазить в окна.

slackwarrior ★★★★★
()
Последнее исправление: slackwarrior (всего исправлений: 1)
Ответ на: комментарий от KivApple

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

Не, не нужно. В hasSomeMethod нам нужен только T* передавать. Остальные мы конструируем там сами, либо с помощью std::declval<Type>() либо просто какими-то известными значениями типа 0 для int

Посмотри:

https://gcc.godbolt.org/z/bfxzKMvvd

fsb4000 ★★★★★
()
Ответ на: комментарий от KivApple

Ещё можно проверять тип который возвращает метод:

просто вместо return true;

возвращаем

return std::is_same_v<type1, type2>

https://gcc.godbolt.org/z/hnj3bYzx8

Ну или если нам не важен конкретный тип, а лишь какое-то свойство то ещё есть:

is_base_of
is_convertible
is_nothrow_convertible

и т.д.

https://en.cppreference.com/w/cpp/types

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от KivApple

допустим, someMethod принимает два аргумента - int& и int

Ах да, std::declval возвращает T&&. для T& нужно свою функцию задекларировать. Типа такого:

template<class T>
typename std::add_lvalue_reference<T>::type declvalLRef() noexcept

https://gcc.godbolt.org/z/ETzoxTW4z

fsb4000 ★★★★★
()
Ответ на: комментарий от KivApple

Если проверять только имя (метода или поля), то может что-то такое работает:

template<typename T>
static constexpr auto hasSomeMethod() -> decltype(&T::someMethod, bool()) {
	return true;
}
template<typename T>
static constexpr auto hasSomeMethod(...) {
	return false;
}
xaizek ★★★★★
()
Последнее исправление: xaizek (всего исправлений: 1)
Ответ на: комментарий от xaizek

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

anonymous
()
Ответ на: комментарий от anonymous

Да, так короче, но Visual Studio 2019 не поддерживает «requires expression» и похоже уже не добавят поддержку. Возможно в Visual Studio 2022 добавят…

fsb4000 ★★★★★
()
Ответ на: комментарий от KivApple

Ещё можно унифицировать, чтобы поддерживались и с помощью концептов и с помощью функций.

#if __cpp_concepts >= 201907L
template<typename T>
concept has_toString = requires(const T& t) {
        t.toString();
    };
#else // ^^ concepts / vv no concepts
template<typename T, typename=decltype(std::declval<T>().toString())>
constexpr bool has_toString_fun(T*)
{
    return true;
}
constexpr bool has_toString_fun(...) {
    return false;
}
template<typename T>
inline constexpr bool has_toString = has_toString_fun((T*)nullptr);
#endif // __cpp_concepts >= 201907L

https://gcc.godbolt.org/z/sxG19Evcr

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

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 2)
Ответ на: комментарий от fsb4000

std::declval<T>() - это не совсем правильный контекст вызова. Лучше использовать самое значение и желательно + forward, т.е. auto && x; ((decltype(x))x).to_string(); Либо через forward, опять же.

Если уже хочется сделать древнее дерьмо, либо под маздайский мусор, то выше уже показывали дефолтный хак через -> decltype() - та можно получить оригинальное значение. Ну и такой колхоз не будет пробивать sfinae.

Ну и использование указателей в С++ - это позорище. Сразу выпиливать нахрен, особенно если это где-то рядом с типизацией.

const T& t - такую бездарную херню тоже писать ненужно. Задача С++ - максимально полно сохранить тип. Хардкорить какие-то мусорные const - это мусорное днище. Это всё древние костыли для передачи rvalue.

Только auto && везде, за редкими исключениями, когда именно принимающая сторона определяет тип.

anonymous
()
Ответ на: комментарий от anonymous

Что ты этим хотел сказал? Бегая по помойка спастил откуда-то это херню и бегаешь-пастишь? Только это не работает. Ты обязан а) сообщить о том, что следует из твоего высера, б) каким именно образом это опровергает мой тезис.

anonymous
()
Ответ на: комментарий от anonymous

каким именно образом это опровергает мой тезис.

«Задача С++ - максимально полно сохранить тип» это не тезис, это пук в лужу. С таким же уровнем информативности можно сказать что-нибудь вроде «Задача С++ - максимально плотно засадить брезентовое поле алюминевыми огурцами».

anonymous
()
Ответ на: комментарий от anonymous

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

Lrrr ★★★★★
()

Как можно сделать опциональный вызов метода класса из шаблона?

через великие и могучие концепты c++20

safocl ★★
()
Ответ на: комментарий от ox55ff

там обычная меташаблонная проверка условия.

safocl ★★
()
Ответ на: комментарий от fsb4000

допустим, someMethod принимает два аргумента - int& и int

Ах да, std::declval возвращает T&&. для T& нужно свою функцию задекларировать.

★★★★★

Этот мир не спасти…

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.