Bug report arising from Symbulation development, content generated via @claude
Describe the bug
emp::vector<T> exposes template <typename InputIt> vector(InputIt first, InputIt last) with no constraint excluding integral types (include/emp/base/vector.hpp:65, :274, :441). When called as emp::vector<T> v(count, {}), overload resolution deduces InputIt = size_t from the first argument and finds {} → size_t (identity conversion) strictly better than {} → T (user-defined conversion, needed by the intended vector(size_t, const T&) fill constructor). The iterator-pair template therefore wins outright and silently constructs from a bogus (count, 0) "range" instead of filling with T{}.
For element types with a non-explicit T(size_t) constructor (e.g. emp::vector<size_t>) this happens to produce output that looks correct by coincidence. For any other type it's a hard compile error, since libstdc++'s own _RequireInputIter SFINAE correctly rejects size_t as an iterator once emp's wrapper has already delegated to it.
To Reproduce
#include "emp/base/vector.hpp"
struct Foo { int x = 0; };
int main() {
size_t n = 5;
emp::vector<Foo> v(n, {}); // intended: n copies of Foo{}
}
error: no matching function for call to 'std::vector<Foo>::vector(long unsigned int&, long unsigned int&)'
...
note: candidate: 'std::vector<_Tp,_Alloc>::vector(size_type, const value_type&, ...)'
note: no known conversion for argument 2 from 'long unsigned int' to 'const Foo&'
Expected behavior
emp::vector<T> v(n, {}) should unambiguously call the vector(size_t, const T&) fill constructor and produce n default-constructed Ts, for any T — matching what a properly SFINAE-guarded iterator constructor (as in std::vector) would do.
Toolchain:
- OS: Ubuntu 24.04
- Compiler: g++ 11.5 / 13.3 / 14.2, clang++ 18.1.3 (all with libstdc++),
-std=c++20
- Empirical Version: current
master (verified against include/emp/base/vector.hpp)
Additional context
Suggested fix — constrain the three iterator-pair constructors to exclude integral types, mirroring std::vector's own guard:
--- a/include/emp/base/vector.hpp
+++ b/include/emp/base/vector.hpp
@@
#include <initializer_list>
#include <iterator>
#include <stddef.h>
+#include <type_traits>
#include <utility>
#include <vector>
@@ (EMP_NDEBUG branch, ~line 65)
- template <typename InputIt>
+ template <typename InputIt, typename = std::enable_if_t<!std::is_integral_v<InputIt>>>
vector(InputIt first, InputIt last) : stdv_t(first, last) {}
@@ (debug branch, ~line 274)
- template <typename InputIt>
+ template <typename InputIt, typename = std::enable_if_t<!std::is_integral_v<InputIt>>>
vector(InputIt first, InputIt last) : stdv_t(first, last), revision(1) {
;
}
@@ (vector<bool> specialization, ~line 441)
- template <typename InputIt>
+ template <typename InputIt, typename = std::enable_if_t<!std::is_integral_v<InputIt>>>
vector(InputIt first, InputIt last) : stdv_t(first, last) {
;
}
Verified locally against g++-11/13/14 and clang++-18: fixes the Foo and nested-emp::vector cases above, and doesn't regress genuine iterator-pair construction from std::vector, std::list, or emp::vector's own iterators.
Bug report arising from Symbulation development, content generated via @claude
Describe the bug
emp::vector<T>exposestemplate <typename InputIt> vector(InputIt first, InputIt last)with no constraint excluding integral types (include/emp/base/vector.hpp:65,:274,:441). When called asemp::vector<T> v(count, {}), overload resolution deducesInputIt = size_tfrom the first argument and finds{} → size_t(identity conversion) strictly better than{} → T(user-defined conversion, needed by the intendedvector(size_t, const T&)fill constructor). The iterator-pair template therefore wins outright and silently constructs from a bogus(count, 0)"range" instead of filling withT{}.For element types with a non-explicit
T(size_t)constructor (e.g.emp::vector<size_t>) this happens to produce output that looks correct by coincidence. For any other type it's a hard compile error, since libstdc++'s own_RequireInputIterSFINAE correctly rejectssize_tas an iterator once emp's wrapper has already delegated to it.To Reproduce
Expected behavior
emp::vector<T> v(n, {})should unambiguously call thevector(size_t, const T&)fill constructor and producendefault-constructedTs, for anyT— matching what a properly SFINAE-guarded iterator constructor (as instd::vector) would do.Toolchain:
-std=c++20master(verified againstinclude/emp/base/vector.hpp)Additional context
Suggested fix — constrain the three iterator-pair constructors to exclude integral types, mirroring
std::vector's own guard:Verified locally against g++-11/13/14 and clang++-18: fixes the
Fooand nested-emp::vectorcases above, and doesn't regress genuine iterator-pair construction fromstd::vector,std::list, oremp::vector's own iterators.