[LLVMdev] Removing std::vector from APIs (was Re: Mutating the elements of a ConstantArray)

Gordon Henriksen gordonhenriksen at me.com
Fri May 15 10:52:04 PDT 2009


On 2009-05-15, at 07:26, David Greene wrote:

> On Friday 15 May 2009 05:50, Jay Foad wrote:
>>
>
>>> The one major thing to be aware of is that it isn't safe to use  
>>> &V[0] when V is an empty std::vector
>>
>> Oh dear. That's a bit of a flaw in the plan. I suppose the solution  
>> is to switch to SmallVector whenever this might be a problem.
>
> Or use iterators.  That's why they're there.

The reason to use the pointer-length pair in preference to iterators  
is that iterators make templates of everything, causing code bloat. In  
my code, rather than pass the separate parameters I use a range<Iter>  
class something like this:

template<typename Iter>
class range {
   Iter Begin, End;

public:
   range() : Begin(), End() { }

   template<typename ConvertibleToRange>
   range(T &seq) : Begin(seq.begin()), End(seq.end()) { }

   Iter begin() { return Begin; }
   Iter end() { return End; }
   // ... other Sequence methods ...
};

And encapsulate the logic to deal with the empty sequence problem in a  
function:

template<typename ConvertibleToRange>
range<typename ConvertibleToRange::value_type*>
ptr_range(ConvertibleToRange &seq) {
   typename ConvertibleToRange::iterator i = seq.begin(), e = seq.end();
   if (i == e)
     return range<typename ConvertibleToRange::value_type*>();
   return range<typename ConvertibleToRange::value_type*>(&*i, &*i +  
(e - i));
};

So that:

StructType::get(v.empty()? 0 : &v[0], v.empty()? 0 : &v[0] + v.size());
// becomes
StructType::get(ptr_range(v));


Two important refinements… First, the above can be made safer so that  
ptr_range will reject e.g. std::list with a bit of metaprogramming:

template<typename T> struct is_ptr { enum { value = false }; };
template<typename T> struct is_ptr<T*> { enum { value = true }; };

template<typename T>
struct is_contiguous_sequence { enum { value =  
is_ptr<T::iterator>::value }; };
template<typename T, typename Alloc>
struct is_contiguous_sequence<std::vector<T,Alloc> > { enum { value =  
true }; };
template<typename T, typename Ch, typename Alloc>
struct is_contiguous_sequence<std::basic_string<T,Ch,Alloc> > { enum  
{ value = true }; };

template<typename T, bool True> struct type_if { typedef T type; }
template<typename T> struct type_if<T,false> { /* cause substitution  
failure */ }

// New prototype for ptr_range.
template<typename ConvertibleRange>
typename type_if<
   range<typename T::value_type*>,
   is_contiguous_sequence<T>::value
 >::type ptr_range(ConvertibleRange &seq);

And secondly, range<T*> can be extended to convert from arrays in  
addition to objects with begin()/end() methods by (1) providing a  
partial specialization on T* which adds a template constructor:

template<typename T>
template<size_t N>
range<T*>::range<T*>(T (&arr)[N]) : Begin(arr), End(arr + N) { }

or by (2a) using a traits type to look up ConvertibleToRange::iterator  
& -::value_type

template<typename T>
struct range_traits {
   typedef typename T::value_type value_type;
   typedef typename T::iterator iterator;
   // .. etc ...
};

template<typename T, size_t N>
struct range_traits<T[N]> {
   typedef T value_type;
   typedef T *iterator;
   // .. etc ...
};

and by (2b) indirecting calls to seq.begin() and seq.end() through  
template functions:

template<typename ConvertibleToRange>
typename range_traits<T>::iterator beginof(T &seq) { seq.begin(); }
template<typename ConvertibleToRange, size_t N>
typename range_traits<T[N]>::iterator beginof(T (&seq)[N]) { return  
seq; }

template<typename ConvertibleToRange>
typename range_traits<T>::iterator endof(T &seq) { seq.begin(); }
template<typename ConvertibleToRange, size_t N>
typename range_traits<T[N]>::iterator endof(T (&seq)[N]) { return seq  
+ N; }

This refines StructType::get(arr, arr + sizeof(arr) / sizeof(arr[0]));
to just StructType::get(arr);

— Gordon





More information about the llvm-dev mailing list