Monomorphized generics trait bounds

why?

template <class T, class U>
size_t func(T a, U b) {
    return a << (b | 1ULL);
}

What is the rationale? This is simple stuff. The compiler should be able to figure it out.
Why not make it optional? Why enforce it?

I'm not sure what you're asking. You've posted a snippet of C++ (I presume) code and asked for a rationale. Since I'm not familiar with C++, can you describe what your code is supposed to demonstrate and how it relates to Rust?

1 Like

That looks like C++. Are you asking about the equivalent Rust code, why trait bounds would be required for the generic types T and U? The reasoning is that it makes the interface of the function fully specify the requirements on its types, without requiring users to read the body of the function (which could be long and could call other functions). It also allows the compiler to not look at the body of the function every time it is used, which speeds up compilation.

3 Likes

Here's the nearest equivalent to that C++ code in Rust:

macro_rules! func {
    ($a:expr, $b:expr) => {{
        $a << ($b | 1u64)
    }};
}

Generics aren't the same thing as templates. Generics are type-checked at declaration, while templates are type-checked at use. The feature Rust has that is most similar to C++ templates, at least with respect to being checked at use, is declarative macros.

Note that Rust macros are also quite different from C preprocessor macros. C macros are expanded and then parsed, whereas Rust macros have to parse before they are expanded (again very like C++ templates).

If you had to order metaprogramming tools by how "smart" they are, you might come up with something like this:

              C macros      C++ templates
    simple    ↓             ↓                             complex
text-based ├──┴──────────┬──┴────────────┬──────────────┤ type-based
     early               ↑               ↑                late
                         Rust macros     Rust generics

("early" and "late" referring to at what stage in the compilation process they are expanded)

9 Likes

Take for example the following program:

#include <vector>
#include <algorithm>
int main()
{
    int a;
    std::vector< std::vector <int> > v;
    std::vector< std::vector <int> >::const_iterator it = std::find( v.begin(), v.end(), a );
}

The compiler should be able to tell that int is not comparable with std::vector<int>. This is also simple stuff, however C++ compiler really struggle with this. The error you get is something like:

In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:71,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/predefined_ops.h: In instantiation of 'bool __gnu_cxx::__ops::_Iter_equals_val<_Value>::operator()(_Iterator) [with _Iterator = __gnu_cxx::__normal_iterator<std::vector<int>*, std::vector<std::vector<int> > >; _Value = const int]':
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:1932:14:   required from '_RandomAccessIterator std::__find_if(_RandomAccessIterator, _RandomAccessIterator, _Predicate, std::random_access_iterator_tag) [with _RandomAccessIterator = __gnu_cxx::__normal_iterator<std::vector<int>*, std::vector<std::vector<int> > >; _Predicate = __gnu_cxx::__ops::_Iter_equals_val<const int>]'
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:1977:23:   required from '_Iterator std::__find_if(_Iterator, _Iterator, _Predicate) [with _Iterator = __gnu_cxx::__normal_iterator<std::vector<int>*, std::vector<std::vector<int> > >; _Predicate = __gnu_cxx::__ops::_Iter_equals_val<const int>]'
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algo.h:3902:28:   required from '_IIter std::find(_IIter, _IIter, const _Tp&) [with _IIter = __gnu_cxx::__normal_iterator<std::vector<int>*, std::vector<std::vector<int> > >; _Tp = int]'
<source>:7:92:   required from here
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/predefined_ops.h:268:17: error: no match for 'operator==' (operand types are 'std::vector<int>' and 'const int')
  268 |  { return *__it == _M_value; }
      |           ~~~~~~^~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:67,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_iterator.h:1064:5: note: candidate: 'template<class _IteratorL, class _IteratorR, class _Container> bool __gnu_cxx::operator==(const __gnu_cxx::__normal_iterator<_IteratorL, _Container>&, const __gnu_cxx::__normal_iterator<_IteratorR, _Container>&)'
 1064 |     operator==(const __normal_iterator<_IteratorL, _Container>& __lhs,
      |     ^~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_iterator.h:1064:5: note:   template argument deduction/substitution failed:
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:71,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/predefined_ops.h:268:17: note:   'std::vector<int>' is not derived from 'const __gnu_cxx::__normal_iterator<_IteratorL, _Container>'
  268 |  { return *__it == _M_value; }
      |           ~~~~~~^~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:67,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_iterator.h:1072:5: note: candidate: 'template<class _Iterator, class _Container> bool __gnu_cxx::operator==(const __gnu_cxx::__normal_iterator<_Iterator, _Container>&, const __gnu_cxx::__normal_iterator<_Iterator, _Container>&)'
 1072 |     operator==(const __normal_iterator<_Iterator, _Container>& __lhs,
      |     ^~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_iterator.h:1072:5: note:   template argument deduction/substitution failed:
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:71,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/predefined_ops.h:268:17: note:   'std::vector<int>' is not derived from 'const __gnu_cxx::__normal_iterator<_Iterator, _Container>'
  268 |  { return *__it == _M_value; }
      |           ~~~~~~^~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:64,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_pair.h:466:5: note: candidate: 'template<class _T1, class _T2> constexpr bool std::operator==(const std::pair<_T1, _T2>&, const std::pair<_T1, _T2>&)'
  466 |     operator==(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
      |     ^~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_pair.h:466:5: note:   template argument deduction/substitution failed:
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:71,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/predefined_ops.h:268:17: note:   'std::vector<int>' is not derived from 'const std::pair<_T1, _T2>'
  268 |  { return *__it == _M_value; }
      |           ~~~~~~^~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:67,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_iterator.h:360:5: note: candidate: 'template<class _Iterator> bool std::operator==(const std::reverse_iterator<_Iterator>&, const std::reverse_iterator<_Iterator>&)'
  360 |     operator==(const reverse_iterator<_Iterator>& __x,
      |     ^~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_iterator.h:360:5: note:   template argument deduction/substitution failed:
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:71,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/predefined_ops.h:268:17: note:   'std::vector<int>' is not derived from 'const std::reverse_iterator<_Iterator>'
  268 |  { return *__it == _M_value; }
      |           ~~~~~~^~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:67,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_iterator.h:398:5: note: candidate: 'template<class _IteratorL, class _IteratorR> bool std::operator==(const std::reverse_iterator<_Iterator>&, const std::reverse_iterator<_IteratorR>&)'
  398 |     operator==(const reverse_iterator<_IteratorL>& __x,
      |     ^~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_iterator.h:398:5: note:   template argument deduction/substitution failed:
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:71,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/predefined_ops.h:268:17: note:   'std::vector<int>' is not derived from 'const std::reverse_iterator<_Iterator>'
  268 |  { return *__it == _M_value; }
      |           ~~~~~~^~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:67,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_iterator.h:1427:5: note: candidate: 'template<class _IteratorL, class _IteratorR> bool std::operator==(const std::move_iterator<_IteratorL>&, const std::move_iterator<_IteratorR>&)'
 1427 |     operator==(const move_iterator<_IteratorL>& __x,
      |     ^~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_iterator.h:1427:5: note:   template argument deduction/substitution failed:
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:71,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/predefined_ops.h:268:17: note:   'std::vector<int>' is not derived from 'const std::move_iterator<_IteratorL>'
  268 |  { return *__it == _M_value; }
      |           ~~~~~~^~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:67,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_iterator.h:1495:5: note: candidate: 'template<class _Iterator> bool std::operator==(const std::move_iterator<_IteratorL>&, const std::move_iterator<_IteratorL>&)'
 1495 |     operator==(const move_iterator<_Iterator>& __x,
      |     ^~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_iterator.h:1495:5: note:   template argument deduction/substitution failed:
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:71,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/predefined_ops.h:268:17: note:   'std::vector<int>' is not derived from 'const std::move_iterator<_IteratorL>'
  268 |  { return *__it == _M_value; }
      |           ~~~~~~^~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:64,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/allocator.h:206:5: note: candidate: 'template<class _T1, class _T2> bool std::operator==(const std::allocator<_Tp1>&, const std::allocator<_T2>&)'
  206 |     operator==(const allocator<_T1>&, const allocator<_T2>&)
      |     ^~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/allocator.h:206:5: note:   template argument deduction/substitution failed:
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:71,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/predefined_ops.h:268:17: note:   'std::vector<int>' is not derived from 'const std::allocator<_Tp1>'
  268 |  { return *__it == _M_value; }
      |           ~~~~~~^~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:67,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_vector.h:1892:5: note: candidate: 'template<class _Tp, class _Alloc> bool std::operator==(const std::vector<_Tp, _Alloc>&, const std::vector<_Tp, _Alloc>&)'
 1892 |     operator==(const vector<_Tp, _Alloc>& __x, const vector<_Tp, _Alloc>& __y)
      |     ^~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_vector.h:1892:5: note:   template argument deduction/substitution failed:
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:71,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/predefined_ops.h:268:17: note:   mismatched types 'const std::vector<_Tp, _Alloc>' and 'const int'
  268 |  { return *__it == _M_value; }
      |           ~~~~~~^~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:64,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/allocator.h:192:7: note: candidate: 'bool std::operator==(const std::allocator<int>&, const std::allocator<int>&)'
  192 |       operator==(const allocator&, const allocator&) _GLIBCXX_NOTHROW
      |       ^~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/allocator.h:192:18: note:   no known conversion for argument 1 from 'std::vector<int>' to 'const std::allocator<int>&'
  192 |       operator==(const allocator&, const allocator&) _GLIBCXX_NOTHROW
      |                  ^~~~~~~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/x86_64-linux-gnu/bits/c++allocator.h:33,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/allocator.h:46,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:64,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/ext/new_allocator.h:171:2: note: candidate: 'template<class _Up> bool __gnu_cxx::operator==(const __gnu_cxx::new_allocator<int>&, const __gnu_cxx::new_allocator<_Tp>&)'
  171 |  operator==(const new_allocator&, const new_allocator<_Up>&)
      |  ^~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/ext/new_allocator.h:171:2: note:   template argument deduction/substitution failed:
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/stl_algobase.h:71,
                 from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/vector:60,
                 from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/predefined_ops.h:268:17: note:   mismatched types 'const __gnu_cxx::new_allocator<_Tp>' and 'const int'
  268 |  { return *__it == _M_value; }
      |           ~~~~~~^~~~~~~~~~~

(code took from code challenge - Generate the longest error message in C++ - Code Golf Stack Exchange)

Because c++ taught us so.

7 Likes

Thanks for the clarification.

Regardless of good error messages, making trait bounds a part of function signature also prevents accidentaly breaking the API. If you break the function signature, you get the error on compile time; even if you currently call the function with appropiate arguments in all of the places in your codebase. Generics specify what the function can do with it's arguments, like how data types specify that for non-generic functions.

4 Likes