7
votes

I read "The C++ Programming language 4th edition, 1st printing, by Bjarne Stroustrup" book (from Amazon.com). Page 785. Stroustrup is explaining how he can eliminate explicit writing of "::type", when using "std::conditional + std::make_unsigned", using "type aliases" (keyword "using"). But that using "type aliases" on "std::conditional + std::make_unsigned" is causing compile errors. By now everything is as it should be. And he goes on to show how to elimintate these compile errors using "delayed evaluation of template type function".

The question is on the line Atype<make_unsigned<string> and myType2<string> ....

I used g++ 4.8.2.

Compile:

g++ -std=c++1y test45.cpp -o a

#include <type_traits>
#include <string>
#include <iostream>
#include <typeinfo>  // for typeid(...)
using namespace std;

template<class T>
struct ErrIndicator {
   typedef ErrIndicator<T> type;
};

template<bool C, class T, class F>
using Conditional = typename conditional<C,T,F>::type;

template<typename T>
using Make_unsigned = typename make_unsigned<T>::type;

template<template<typename ...> class F, typename... Args>
using Delay = F<Args ...>;

template<class T>
using myType1 = Conditional<is_integral<T>::value,
    Make_unsigned<T>,
    ErrIndicator<T>
    >;

template<class T> 
    using myType2 = Conditional<is_integral<T>::value,
    Delay<Make_unsigned, T>,   // delayed evaluation
    ErrIndicator<T>
    >;

template<class T>
    using myType4 = Conditional<is_integral<T>::value,
    make_unsigned<T>,
    ErrIndicator<T>
    >;

template<typename T>
class Atype {}; 

template<typename T>
void func1(T &ia /* output param */) {
  cout << "unsigned integral type" << endl;
  ia = 4;  // "unsigned integral type" computation
}

template<typename T>
void func1(ErrIndicator<T> &) {
  cout << "non integral type: " << typeid(T).name() << endl;
}

int main() {
  myType1<int> var1a; // OK
  // myType1<string> var1b; // Error; The book says error
  //                    // should occur here. Here I understand.
  myType2<int> var2a; // OK
  // myType2<string> var2b; // Error - why?. Maybe I didn't get it,
  //                      // but I understand the book as no
  //                      // error should occur here.
  //                      // @DyP answered it.
  Atype<make_unsigned<string> > var3;  // OK here, look below at @DyP
  //                             // for "foo, bar, X" why
  //                        // make_unsigned<string> is not an error here.
  // make_unsigned<string> var6; // Error
  // Atype<make_unsigned<string>::type > var4;  // Error
  Atype<make_unsigned<int>::type > var5;  // OK
  //-------------
  myType4<string>::type var7;    // Look below for "myType3", where @Yakk
  //                          // obviates the necessity to write "::type".
  // rsl7 = 1:
  cout << "rsl7 = " << is_same<decltype(var7), ErrIndicator<string> >::value << endl; 
  func1(var7);  // "non integral type" overload of func1()
  //---------
  myType4<int>::type var8;
  // rsl8 = 1:
  cout << "rsl8 = " << is_same<decltype(var8), unsigned int>::value << endl; 
  func1(var8);  // "unsigned integral type" overload of func1()
}
2
The error for string should be an error. make_unsigned requires its template argument to be of integral, but not bool type. As I understand it, it doesn't care that you don't use the nested type.dyp
What do you think Delay is doing? And what quote from the book makes you think that? I mean, delayed evaluation could help, but what makes you think Delay does that?Yakk - Adam Nevraumont
@Yakk I think it's supposed to delay the evaluation of the metafunction Make_unsigned until it has been checked that the type is integral.dyp
@DyP sure, but other than its name, why would it do so?Yakk - Adam Nevraumont
@Yakk Stroustrup introduces a conditional< is_integral<T>::value, make_unsigned<T>, Error<T> >::type and talks about that using this instead of typename make_unsigned<T>::type would prevent a compile-time error.dyp

2 Answers

3
votes

I think Stroustrup intents to delay the access to make_unsigned<T>::type, because this nested type isn't defined for non-integral types. However, using an alias template seems not to be enough for clang++ and g++: They resolve Delay<Make_unsigned,T> directly to Make_unsigned<T>, and this to make_unsigned<T>::type.

The whole example is:

template<typename C, typename T, typename F>
using Conditional = typename std::conditional<C,T,F>::type;

template<typename T>
using Make_unsigned = typename std::make_unsigned<T>::type;

// the example
Conditional<
  is_integral<T>::value,
  Delay<Make_unsigned,T>,
  Error<T>
>

// "The implementation of a perfect `Delay` function is nontrivial,
//  but for many uses this will do:"
template<template<typename...> class F, typename... Args>
using Delay = F<Args...>;

The question is of course, when is Delay<Make_Unsigned,T> resolved? For class templates (not alias templates), they're only instantiated implicitly when a complete object type is required or the semantics of the program are affected. Consider:

#include <type_traits>
using namespace std;

template<class T>
struct foo
{
    static_assert(is_same<T, void>{}, "!");
};

template<class X>
struct bar
{
    // without the line below, no error!
    //X x;
};

int main()
{
    bar<foo<int>> b;
}

This however is not the case for alias templates. They're substituted [temp.alias]/2

When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template.

IMHO, this suggests that in the example above, Delay<Make_unsigned,T> is equivalent to make_unsigned<T>::type, which will instantiate make_unsigned<string>::type and cause a compile-time error.

2
votes
template<bool, template<typename...>class Lhs, template<typename...>class Rhs, typename... Ts>
struct conditional_apply {
  typedef Lhs<Ts...> type;
};
template<template<typename...>class Lhs, template<typename...>class Rhs, typename...Ts>
struct conditional_apply<false, Lhs, Rhs, Ts...> {
  typedef Rhs<Ts...> type;
};
template<bool b, template<typename...>class Lhs, template<typename...>class Rhs, typename... Ts>
using Conditional_apply=typename conditional_apply<b, Lhs, Rhs, Ts...>::type;

template<class T> using myType3 = Conditional_apply<is_integral<T>::value, Make_unsigned, ErrIndicator, T >;

should do actual delayed application of templates on T, barring typos (on phone).