Как проверить, определил ли пользователь метод?
От: Kazmerchuk Pavel  
Дата: 02.06.22 19:53
Оценка:
Есть код, который успешно работал под MSVC https://gcc.godbolt.org/z/hrYeM4dc7
#include <iostream>

template<class D>
struct Base {

    template<class T>
    void foo(T val) { 
        static_assert(false, "Error! User has not defined the 'foo(...)'");
        std::cout << "Base::foo\n"; 
    }

    void call_foo(int val) {
        static_cast<D*>(this)->foo(val);
    }
};

struct Derived : public Base<Derived> {

    template<class T>
    void foo(T val) { std::cout << "Derived::foo\n"; }
};

struct DerivedFail : public Base<DerivedFail> {

    template<class T>
    void foo(T val) { std::cout << "DerivedFail::foo\n"; }
};

int main() {
    
    Derived().call_foo(1);
    DerivedFail().call_foo(1);

    return 0;
}

Если закомментировать DerivedFail::foo срабатывал static_assert.

После одного из последних обновлений MSVC (не знаю точно после какого) static_assert срабатывает всегда.
GCC ведет себя также https://gcc.godbolt.org/z/3vz436sn9
Как вылечить?
Re: Как проверить, определил ли пользователь метод?
От: Videoman Россия https://hts.tv/
Дата: 02.06.22 20:36
Оценка:
Здравствуйте, Kazmerchuk Pavel, Вы писали:

KP>Как вылечить?


Код работал из-за того, что компилятор MS слишком вольно обращался со стандартом. Попробуй сделать что бы 'false' зависел от шаблонного параметра: static_assert(sizeof(T) == 0, "Error! User has not defined the 'foo(...)'");
Отредактировано 02.06.2022 20:36 Videoman . Предыдущая версия .
Re[2]: Как проверить, определил ли пользователь метод?
От: σ  
Дата: 02.06.22 21:11
Оценка:
V>Код работал из-за того, что компилятор MS слишком вольно обращался со стандартом.

А что нарушалось?

V> Попробуй сделать что бы 'false' зависел от шаблонного параметра: static_assert(sizeof(T) == 0, "Error! User has not defined the 'foo(...)'");


Завтра обновится, и такой сорт IFNDR тоже перестанет компилироваться.
Re[3]: Как проверить, определил ли пользователь метод?
От: Videoman Россия https://hts.tv/
Дата: 02.06.22 21:19
Оценка:
Здравствуйте, σ, Вы писали:

σ>А что нарушалось?


Я не уверен на 100%, но насколько я понимаю, в этом случае старые MS компиляторы проверяли только корректнось синтаксиса. Если метод не инстанцируется, то там вообще можно писать любой мусор, лишь бы он был корректен с точки зрения абстрактного С++. Ах сколько у меня кода перестало работать шаблонного, из-за того, что иногда не умышленно дергаешь реализацию, которая не объявлена в точке использования .

σ>Завтра обновится, и такой сорт IFNDR тоже перестанет компилироваться.


Не, теперь всё по стандарту.
Re[4]: Как проверить, определил ли пользователь метод?
От: σ  
Дата: 02.06.22 21:39
Оценка: +1
σ>>А что нарушалось?

V>Я не уверен на 100%, но насколько я понимаю, в этом случае старые MS компиляторы проверяли только корректнось синтаксиса. Если метод не инстанцируется, то там вообще можно писать любой мусор, лишь бы он был корректен с точки зрения абстрактного С++.


Не уверен на 100%, но насколько я понимаю, можно даже корректность синтаксиса у шаблона не проверять (см. g в https://timsong-cpp.github.io/cppwp/n4861/temp.res#8.example-1)
Возможно, пример некорректный и грамматика template-declarationtemplate-head declaration намекает, что declaration должен быть синтаксически корректен даже без инстанциаций? А может и нет.

σ>>Завтра обновится, и такой сорт IFNDR тоже перестанет компилироваться.


V>Не, теперь всё по стандарту.


Совершенно верно, IFNDR по стандарту (https://timsong-cpp.github.io/cppwp/n4861/temp.res#8.1)
Re[5]: Как проверить, определил ли пользователь метод?
От: watchmaker  
Дата: 03.06.22 00:29
Оценка: 15 (3)
Здравствуйте, σ, Вы писали:


σ>>>Завтра обновится, и такой сорт IFNDR тоже перестанет компилироваться.


V>>Не, теперь всё по стандарту.


σ>Совершенно верно, IFNDR по стандарту (https://timsong-cpp.github.io/cppwp/n4861/temp.res#8.1)


Ничего страшного, обязательные [[no_unique_address]] и еmpty base optimization уже существуют в языке, скоро при движении в эту сторону и полноценные типы нулевого размера появятся. Так что этот пункт будет не применим.
Но, конечно, способу Videoman'a
Автор: Videoman
Дата: 02.06.22
это не сильно поможет: сейчас он неправилен лишь формально и если придираться, а после станет просто неправильным
А если серьёзно, то у sizeof(T)==0 есть и другие недостатки: например, с void не работает, и с неполными типами тоже. Не советую так писать.

Вместо этого есть классический способ
template <typename Args...>
inline constexpr bool dependant_false = false;
(или эквивалентный код на шаблонных структурах для C++03)

Так как формально остаётся возможность сделать специализацию с true, то нельзя утверждать, что не существует такого типа, для которого static_assert пройдёт. И поэтому static_assert(dependant_false<T>) можно использовать.
Этот способ даже в стандарт почти добавили, раз все всё равно все копипастят эти две строчки: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1830r1.pdf — .
Правда на этом этапе него есть и более красивый конкурент: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2593r0.html
Re[6]: Как проверить, определил ли пользователь метод?
От: Kazmerchuk Pavel  
Дата: 04.06.22 16:39
Оценка:
Здравствуйте, watchmaker, Вы писали:

Спасибо!
Re: Как проверить, определил ли пользователь метод?
От: sergii.p  
Дата: 07.06.22 09:13
Оценка: -1
Здравствуйте, Kazmerchuk Pavel, Вы писали:

KP>Как вылечить?


может есть возможность вообще переписать код?
Два стандартых способа решить проблему:

либо использовать std::any

struct Base
{
    virtual void foo(std::any v) = 0;
};

struct Derived: Base
{
    virtual void foo(std::any v) override {};
};


либо убрать наследование вовсе

struct Derived1
{
    template<typename T> void foo(T v){}
};

struct Derived2
{
    template<typename T> void foo(T v){}
};

using Base = std::variant<Derived1, Derived2>;

...

Base b = Derived1{};
std::invooke(b, [](auto& b){ b.foo(); });
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.