Нарушение ODR на концептах
От: Кодт Россия  
Дата: 28.05.21 20:32
Оценка: 49 (5)
Код для поиграться: https://gcc.godbolt.org/z/oeznrcGTa
clang и gcc ведут себя несколько по-разному, кстати.

Пусть у нас есть два концепта — Ptr и Vec (тип является голым указателем и вектором, соответственно).
Напишем три шаблона foo(const T&).

А потом попробуем вызвать для вектора / указателя на вектор / вектора векторов.
#include <iostream>
#include <vector>

template<class T> constexpr bool is_vector = false;
template<class T> constexpr bool is_vector<std::vector<T>> = true;
template<class T> concept Vec = is_vector<T>;

template<class T> constexpr bool is_pointer = false;
template<class T> constexpr bool is_pointer<T*> = true;
template<class T> concept Ptr = is_pointer<T>;

template<class T> void foo(T  ) { std::cout << "def) " << __PRETTY_FUNCTION__ << std::endl;            }
template<Ptr   T> void foo(T t) { std::cout << "ptr) " << __PRETTY_FUNCTION__ << std::endl; foo(*t);   }
template<Vec   T> void foo(T t) { std::cout << "vec) " << __PRETTY_FUNCTION__ << std::endl; foo(t[0]); }

int main() {
    std::vector v { 1 };

    auto pv = &v;
    auto ppv = &pv;

    std::vector vv {{ v }};
    std::vector vvv {{ vv }};

    foo(ppv);
    std::cout << std::endl;
    
    foo(v);
    std::cout << std::endl;

    foo(vvv);
}

В зависимости от того, что именно и в каком порядке мы напишем в main, получим разные результаты — включая ошибку линковки!

В чём тут проблема: определение foo<Ptr> видит только объявления foo<class> и само себя, естественно, — а foo<Vec> ещё не видит.
Поэтому foo(vector<int>) оказывается неоднозначным.

С одной стороны, компилятор инстанцирует его из foo(vector<int>*) как foo<class=vector<int>>;
с другой стороны, из main и/или из foo<Vec> — как foo<Vec=vector<int>>.

Ограничения не входят в манглированное имя (мы можем объявить параметр как концепт, или можем выписать requires, — эффект будет тот же).
Поэтому компилятор не различает эти функции, и мы отхватываем нарушение ODR.
Он или берёт ранее инстанцированную функцию из своего кеша, или принудительно создаёт ещё один объект в объектном файле, удивляя линкера. А при оптимизациях, подозреваю, начнёт хаотически инлайнить / вызывать что бог на душу положит.

Как исправить программу — очевидно. Надо выписать объявления до определений. (Минимально достаточно в данном случае вытащить третье объявление перед вторым определением).
Или, как вариант, запихнуть шаблоны функций в class scope, — там правила видимости другие, все видят всех сразу.

Вопрос в другом.

ЧТО ЭТО?
— дефект стандарта
— дефект правил манглирования имён
— или тут по стандарту должно было возникнуть ODR, и спрос исключительно с клиентского кода?
Перекуём баги на фичи!
Отредактировано 28.05.2021 20:35 Кодт . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.