Skip to content

emp::vector's unconstrained iterator-pair constructor hijacks vector(count, {}) calls #547

Description

@mmore500

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions