Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does passing to a function a set::iterator instead of a const_iterator violate the One Definition Rule?

The description of the std::set container given by cppreference.com contains this note at the end:

The member types iterator and const_iterator may be aliases to the same type. Since iterator is convertible to const_iterator, const_iterator should be used in function parameter lists to avoid violations of the One Definition Rule.

I don't understand this last remark. What I understand is that a set doesn't allow modifying its elements (if you need to change one, you have to erase it and then insert the new one), so every iterator works as a const_iterator. The standard adds that it is possible (but not required) that they are the same type. So far it's clear.

What I don't get is the possible violation of the One Definition Rule. That rule says that a function can have many declarations, but only one definition. How is that violated? Let's say I have a set<int>, and I create a function which takes as argument an iterator. Since they work the same way, I can choose its type: either set<int>::iterator or set<int>::const_iterator. What happens if I do not follow the advice, that is, I choose set<int>::iterator?

I've written a program to try to find an answer. Basically there are 2 functions, one accepting an iterator and the other accepting a const_iterator, and I call each of them twice, once passing an iterator and once passing a const_iterator. Here it is:

#include <iostream>
#include <set>

void print_set_iter(std::set<int>::iterator& it) {
    std::cout << "Set element from       iterator: " << *it       << "\n";
}

void print_set_const_iter(std::set<int>::const_iterator& const_it) {
    std::cout << "Set element from const_iterator: " << *const_it << "\n";
}


int main() {
    std::set<int> primes = {2, 3, 5, 7, 11};
    std::set<int>::iterator             it = primes.find(3);
    std::set<int>::const_iterator const_it = primes.find(5);

    print_set_iter(it);
    print_set_iter(const_it);

    print_set_const_iter(it);
    print_set_const_iter(const_it);
}

I've compiled this on rextester.com using the 3 most popular compilers (gcc, clang, MSVC): there are no warnings, and it runs fine. Normally I'd expect print_set_iter(const_it); to cause an error, but it doesn't. Does it mean that these 3 implementations are using the same type for both iterators? But even in that case, and even if I found a compiler that doesn't use the same type for these iterators, I still don't understand why there would be an ODR violation. If the types were different, the forbidden conversion (from const to non-const) should trigger an error, but that has nothing to do with ODR. Can anyone show me an example of that violation, or explain what that note means?

like image 839
Fabio says Reinstate Monica Avatar asked Jun 21 '19 09:06

Fabio says Reinstate Monica


People also ask

What is the difference between iterator and Const_iterator?

A const iterator points to an element of constant type which means the element which is being pointed to by a const_iterator can't be modified. Though we can still update the iterator (i.e., the iterator can be incremented or decremented but the element it points to can not be changed).

What is the point of iterators?

The primary purpose of an iterator is to allow a user to process every element of a container while isolating the user from the internal structure of the container. This allows the container to store elements in any manner it wishes while allowing the user to treat it as if it were a simple sequence or list.


2 Answers

you must name the functions the same way to get the error. Changed code: https://rextester.com/SSNZ54459

And the error is

source_file.cpp: In function ‘void print_set_iter(std::set<int>::const_iterator&)’:
source_file.cpp:8:6: error: redefinition of ‘void print_set_iter(std::set<int>::const_iterator&)’
 void print_set_iter(std::set<int>::const_iterator& const_it) {
      ^
source_file.cpp:4:6: note: ‘void print_set_iter(std::set<int>::iterator&)’ previously defined here
 void print_set_iter(std::set<int>::iterator& it) {
      ^
like image 53
Zefick Avatar answered Oct 17 '22 18:10

Zefick


There are two maybes:

  • the type aliases may or may not refer to the same type;
  • ODR violation is not required to be diagnosed, so you cannot disprove its existence by (even numerous) examples.

If the aliased types are the same, then it is an ODR violation, same as this:

using type_1 = int;
using type_2 = int;

void func(type_1) {}
void func(type_2) {}

The aliased types are considered in the signature, not an arbitrary number of aliases that can be created for each type. The signature of the two definitions above is the same.

like image 8
LogicStuff Avatar answered Oct 17 '22 18:10

LogicStuff