bobster
bobster

Reputation: 53

Compiling template scalar vector addition operator when operands are of type short fails

I am using auto, decltype and declval in a simple vector class in order to perform basic vector operations e.g. addition of a scalar and a vector. However, I have trouble making it work when trying to add a scalar of type short and a vector of type short.

// Vector.h
#include <iostream>
#include <vector>
#include <algorithm>
#include <cassert>
using namespace std;

template<typename T> class Vector;
template<typename T> std::ostream& operator<< ( std::ostream& s, const Vector<T>& other );

template<typename T>
class Vector {
  std::vector<T> base;

  // vector + scalar
  template<typename T1, typename T2>
  friend auto operator+(const Vector<T1> & lhs, const T2 & scalar) -> Vector<decltype(std::declval<T1>() + std::declval<T2>())>;         

  friend std::ostream& operator<< <T> ( std::ostream& s, const Vector<T>& other );

public:
  Vector();
  Vector( const Vector<T>& other );
  Vector<T>& operator= ( const Vector<T> &other );
  auto& operator[] ( int i );
  void insert( const T element );
};

template<typename T>
Vector<T>::Vector() {
}

template<typename T>
Vector<T>::Vector( const Vector<T>& other ) {
  base = other.base;
}

template<typename T>
Vector<T>& Vector<T>::operator= ( const Vector<T> &other ) {
  base = other.base;
}

template<typename T>
auto& Vector<T>::operator[] ( int i ) {
  assert( i >= 0 && i < base.size() );
  return base[i];
}

template<typename T>
void Vector<T>::insert( const T element ) {
  base.push_back( element );
}

// vector + scalar
template<typename T1, typename T2>
auto operator+(const Vector<T1> & lhs, const T2 & scalar)
  -> Vector<decltype(std::declval<T1>() + std::declval<T2>())>
{
  typedef decltype(std::declval<T1>() + std::declval<T2>()) T3;
  Vector<T3> result;
  result.base.reserve(lhs.base.size());
  std::transform(lhs.base.begin(), lhs.base.end(), std::back_inserter(result.base),
                 [&scalar](const T1 & element) { return element + scalar; });
  return result;
}

Test program:

// vector_test.cpp

void test_vector_int_scalar_int_addition() {
  Vector<int> v;
  v.insert( 1 );
  v.insert( 2 );
  v.insert( 3 );
  int s = 2;
  Vector<int> res = v + s;
  }

void test_vector_short_scalar_short_addition() {
   Vector<short> v;
   v.insert( 1 );
   v.insert( 2 );
   v.insert( 3 );
   short s = 2;
   Vector<short> res = v + s;
}

int main() {
   test_vector_int_scalar_int_addition(); // Compiles and works
   test_vector_short_scalar_short_addition(); // Does NOT compile
}

Compiling the above fails for the case using short "test_vector_short_scalar_short_addition()", but works fine when using int "test_vector_int_scalar_int_addition()". I get the following error:

 error: no viable conversion from 'Vector<decltype(std::declval<short>() + std::declval<short>())>' to 'Vector<short>'

What puzzles me is that is works fine for other types e.g. double, float, long etc. but appears to fail only when using short.

Compiler info:

clang++ --version
Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)

Upvotes: 2

Views: 645

Answers (2)

bogdan
bogdan

Reputation: 9317

The operands of the built-in addition operator undergo the usual arithmetic conversions, which include integral promotions, which means that most integral types that have an integer conversion rank less than that of int are converted to int.

The above means that your Vector<decltype(std::declval<short>() + std::declval<short>())> is actually Vector<int>, and the compiler complains that it can't convert it to Vector<short>.

This can be verified using the following code:

#include <iostream>
#include <type_traits>

int main()
{
    std::cout << std::is_same<decltype(std::declval<short>() + std::declval<short>()), int>::value << '\n';
}

which prints 1.


As an alternative for determining the type of the result, you could consider std::common_type. This will give you a short if both operands are short, but will still apply the usual arithmetic conversions if the types are different - for example, unsigned short and short will typically yield int (if sizeof(short) < sizeof(int)).

Upvotes: 5

David G
David G

Reputation: 96810

The above behavior can be reproduced by this simple example:

short a = 1, b = 1;
auto res = a + b; // decltype(res) == int

As part of the usual arithmetic conversions, both operands (shorts) undergo integral promotion and are converted to int. This is not the case with float or double as they are floating-point types. Since the rank of short is less than that of int, the result of the operation is converted to int, if it can be.

http://en.cppreference.com/w/c/language/conversion#Usual_arithmetic_conversions

Upvotes: 1

Related Questions