std::map неконстантные функторы, ошибка времени компиляции в clang++, нормально для g++

Мне любопытно, знает ли кто-нибудь, почему g++ компилирует приведенный ниже код, а clang++ выдает ошибку. Код создает std::map<int, int, SortCriterion> с пользовательским функтором сортировки SortCriterion. Через конструктор SortCriterion можно указать тип сортировки: по возрастанию или по убыванию. Сравнение ключей реализовано через operator()(int, int). Под g++ все компилируется и работает нормально, даже с -Wall, -Wextra, Wpedantic и т.д. Однако clang++ выдает ошибку при вызове функции insert и жалуется на const-ность оператора сравнения, т.е. хочет operator()(int, int) const.

note: candidate function not viable: 'this' argument has type
      'const SortCriterion', but method is not marked const
    bool operator()(int x, int y) //const

Теперь я знаю, что с ключами в ассоциативных контейнерах не следует связываться, так как это повредит внутреннюю структуру контейнера, но обеспечивается ли const-ность STL? Кажется, что clang++ ожидает std::map<key, value, const comparator>, тогда как g++ не навязывает const.

PS: g++4.9, Apple LLVM version 5.1 (clang-503.0.40)

#include <map>
#include <string>
#include <iostream>

using namespace std;

class SortCriterion
{
    bool ascending;
public:
    SortCriterion(bool sorting_type): ascending(sorting_type){};
    // if operator below is not marked const, 
    // clang++ spits a compile-time error on insert
    // however g++ compiles it ok
    bool operator()(int x, int y) //const
    {
        if(ascending) // ascending
            return x<y;
        else
            return x>y;
    }
};

int main()
{
    map<int, string, SortCriterion> m(SortCriterion(false)); // ascending

    m.insert({1,"one element"}); // clang++ compile error here
    m.insert({2,"another element"});
    for(auto elem: m)
        cout << elem.first << ": " << elem.second << endl;
}

person vsoftco    schedule 29.07.2014    source источник
comment
@MattMcNabb, спасибо! Я действительно только сейчас понял, что мой вопрос может быть обманом, stackoverflow.com/q/13148513/3093378, и действительно operator() должен быть помечен как const, чтобы иметь возможность использовать const ссылки на maps. Тогда я думаю, что libstdc++ есть ошибка.   -  person vsoftco    schedule 29.07.2014
comment
Эта проблема, кажется, решена в удаленном ответе на сообщение, на которое вы ссылаетесь (что я вижу из-за наличия 10 тысяч представителей, но вы не сможете этого сделать). Решение состоит в том, чтобы отметить, что в таблице 102 указано, что при передаче c в качестве аргумента конструктору map тогда c должен иметь тип CopyConstructible и использовать копию c в качестве объекта сравнения. Таким образом, на самом деле нет спецификации, что функция operator() должна быть const.   -  person M.M    schedule 29.07.2014
comment
@MattMcNabb хммм, тогда, если operator() не применяется const, то задача состоит в том, чтобы обеспечить const-ность для const ссылок, иначе это не сработает для const map&. Итак, в заключение g++ прав? Может быть, вы можете опубликовать это как ответ, чтобы в будущем его можно было найти.   -  person vsoftco    schedule 29.07.2014
comment
@MattMcNabb Я не согласен, в удаленном ответе говорится, что тип параметра конструктора map равен const Compare&, operator() также должен быть const, но это неверно. map может (и будет) делать копию объекта компаратора. Аргументы Керрека о том, что требования различных map функций-членов требуют, чтобы operator() компаратора было const, гораздо более убедительны. Например, gcc подавится кодом выше, если вы добавите строку auto c = m.count(1);.   -  person Praetorian    schedule 29.07.2014
comment
@Praetorian, это была моя точка зрения. Вероятно, я плохо сформулировал это, поэтому я попробую еще раз: тот факт, что конструктор определен как принимающий const Compare &, не означает, что компаратор должен быть const (однако могут быть другие пункты, которые подразумевают, что компаратор должен быть константным)   -  person M.M    schedule 29.07.2014
comment
@Praetorian задыхается, потому что использует operator() для проверки эквивалентности?   -  person vsoftco    schedule 29.07.2014
comment
@vsoftco Да, map::count является константной функцией-членом, поэтому, когда она пытается вызвать неконстантную operator() для вашего компаратора, возникает ошибка.   -  person Praetorian    schedule 29.07.2014
comment
@vsoftco: иначе это не сработает для const map& - я не совсем понимаю, что вы имеете в виду, но это кажется неправильным ;-). Даже с const map функции-члены map могут создать объект Compare(), отличный от const, и действительно есть функция key_compare map::key_comp() const;, которая явно возвращает объект, отличный от const key_compare, который является typedef для параметра шаблона Compare. Итак, да, g++ кажется правильным, но, несмотря на это, есть много намеков на то, что, вероятно, ожидалась версия const: по умолчанию Compare - std::less - определяет версию const, ...   -  person Tony Delroy    schedule 29.07.2014
comment
...дальше есть map::value_compare, в котором operator() равно const. Есть места (Таблица 102), где указано «Использует Compare() в качестве объекта сравнения» — это тонко намекает на временное значение, что означает, что могут быть вызваны только const членов. Конечно, есть и тот факт, что operator должно логически быть const, независимо от того, предписывает это Стандарт или нет.   -  person Tony Delroy    schedule 29.07.2014
comment
@TonyD, я имел в виду, что передача map по ссылке const не позволит вам вызвать его компаратор, отличный от const, поэтому логично пометить его const   -  person vsoftco    schedule 29.07.2014
comment
@vsoftco: но это неправда ... любая функция-член const map, которую вы вызываете, может свободно кодировать Compare c; if c(a, b) .... (В Стандарте ничего не говорится о том, что объект Compare map также не хранится вместе с mutable, хотя это разумное предположение.)   -  person Tony Delroy    schedule 29.07.2014
comment
Я думаю, что другой вопрос - обман, однако принятый ответ, похоже, не решает его (в свете точки зрения Тони Д здесь)   -  person M.M    schedule 29.07.2014
comment
@TonyD это становится сложнее, чем я думал ... Я не понимаю, почему const map может вызывать неконстантный функтор (последний может иметь побочный эффект). Если я попытаюсь сделать то, что вы предлагаете, я получу passing const Foo<SortCriterion> as this argument of void Foo<Compare>::do_something() [with Compare = SortCriterion] discards qualifiers [-fpermissive] foo.do_something();   -  person vsoftco    schedule 29.07.2014
comment
@vsoftco эта ошибка говорит о вызове функции non_const do_something для константного объекта   -  person M.M    schedule 29.07.2014
comment
@MattMcNabb это то, что я имел в виду, когда говорил о передаче карты по константной ссылке. Разве у вас нет объекта const? И если да, то вы не сможете использовать operator() из функтора, если он не помечен const. Может быть, я что-то упускаю, извините за слишком много комментариев, но хотелось бы понять, что происходит.   -  person vsoftco    schedule 29.07.2014
comment
@vsoftco пример   -  person M.M    schedule 29.07.2014
comment
@vsoftco: вы правы в том, что, учитывая const object, вы не можете использовать не-const функции элемента данных, отличного от mutable, но ключевым моментом здесь было то, был ли объект Compare, используемый для сравнения, обязательно членом данных map, а не его не-const копия, сделанная в функции-члене карты, желающей выполнить сравнение. В любом случае, я думаю, что ответ, который я опубликовал, показывает еще одно убедительное доказательство того, что версия, отличная от const, необходима для работы некоторых функций map. Конечно, с шаблонами компилятор не обязательно проверяет все, что не вызывается.   -  person Tony Delroy    schedule 29.07.2014
comment
почему вы можете использовать {} для вставки элемента, например m.insert({1,"one element"});, но не std::make_pair(1,"one element")? Я не могу скомпилировать это с VS2012, это новый синтаксис С++ 14?   -  person Marson Mao    schedule 29.07.2014
comment
@MarsonMao void insert (initializer_list<value_type> il); — это C++11 новый конструктор, использующий списки инициализаторов. См. en.cppreference.com/w/cpp/container/map/insert   -  person vsoftco    schedule 29.07.2014


Ответы (1)


Стандарт С++ 11 документирует член std::map:

class value_compare {
    friend class map;
protected:
    Compare comp;
    value_compare(Compare c) : comp(c) {}
public:
    typedef bool result_type;
    typedef value_type first_argument_type;
    typedef value_type second_argument_type;
    bool operator()(const value_type& x, const value_type& y) const {
        return comp(x.first, y.first);
    }
};

...учитывая, что value_compare::operator() является const, он не может вызвать участника, не являющегося const, на comp. Это означает, что Compare обязательно должен иметь const operator(), чтобы эту часть API можно было использовать.

person Tony Delroy    schedule 29.07.2014
comment
Это пахнет дефектом, кажется странным, что неконстантный компаратор будет в порядке для всех операций, кроме value_comp ... который все равно не используется картой - person M.M; 29.07.2014
comment
vector допускает различные операции в зависимости от свойств типа шаблона (например, объекты, не конструируемые из dfeault, могут быть emplace_back'd, но размер вектора не может быть изменен), поэтому, возможно, map разрешает неконстантный компаратор для всех операций, кроме value_comp. (По букве стандарта, т. е. все равно кажется бракованным) - person M.M; 29.07.2014
comment
@MattMcNabb откуда вы знаете, что value_compare не используется в реализации map? - person vsoftco; 29.07.2014
comment
@vsoftco ну, это не говорит, что это - person M.M; 29.07.2014