12

I have a problem in my application where I'd like to assert that a function application would be rejected by the compiler. Is there a way to check this with SFINAE?

For example, assume that I'd like to validate that std::transform to a const range is illegal. Here's what I have so far:

#include <algorithm>
#include <functional>
#include <iostream>

namespace ns
{

using std::transform;

template<typename Iterator1, typename Iterator2, typename UnaryFunction>
  struct valid_transform
{
  static Iterator1 first1, last1;
  static Iterator2 first2;
  static UnaryFunction f;

  typedef Iterator2                   yes_type;
  typedef struct {yes_type array[2];} no_type;

  static no_type transform(...);

  static bool const value = sizeof(transform(first1, last1, first2, f)) == sizeof(yes_type);
};

}

int main()
{
  typedef int *iter1;
  typedef const int *iter2;
  typedef std::negate<int> func;

  std::cout << "valid transform compiles: " << ns::valid_transform<iter1,iter1,func>::value << std::endl;

  std::cout << "invalid transform compiles: " << ns::valid_transform<iter1,iter2,func>::value << std::endl;

  return 0;
}

Unfortunately, my trait rejects both the legal and the illegal cases. The result:

$ g++ valid_transform.cpp 
$ ./a.out 
valid transform compiles: 0
invalid transform compiles: 0
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
Jared Hoberock
  • 11,118
  • 3
  • 40
  • 76
  • `constexpr` might help, just a quick thought. – chris May 05 '12 at 00:41
  • The other problem is that `std::cout << sizeof(std::transform(iter1(), iter1(), iter2(), func()));` compiles, while `std::cout << std::transform(iter1(), iter1(), iter2(), func());` does not. – Lol4t0 May 05 '12 at 07:07
  • @Lol4t0 That is because `sizeof()` does not evaluate it's arguments during compile time. – TemplateRex May 05 '12 at 09:26

2 Answers2

4

Your question is similar to SFINAE + sizeof = detect if expression compiles.

Summary of that answer: sizeof evaluates the type of the expression passed to it, including instantiating a function template, but it does not generate a function call. This is the reason behind Lol4t0's observations that sizeof(std::transform(iter1(), iter1(), iter2(), func())) compiles even if std::transform(iter1(), iter1(), iter2(), func()) does not.

Your concrete problem can be solved by evaluating the template from Lol4t0's answer for any output range that is to be supplied to std::transform. However, the general problem of verifying in a template that a function call will compile appears to be impossible to be solved with the sizeof + SFINAE trick. (it would require a compile-time expression that is derivable from a run-time function call).

You might want to try ConceptGCC to see if this allows you to express the requisite compile-time checking in a more convenient way.

Community
  • 1
  • 1
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Your `OutputIterator` type can completely differ from `InputIterator` type, it depends on `Operator` return type – Lol4t0 May 05 '12 at 12:12
  • @Lol4t0 It only depends on whether OutIt can be converted to InIt. – TemplateRex May 05 '12 at 12:37
  • You don't get the point: `OutputIterator` type can completely differ from `InputIterator` type, **but** `std:transform` still would work (and your validation will fail). [Example](http://ideone.com/12qfo). – Lol4t0 May 05 '12 at 15:54
  • Also note, that `std::is_const; std::is_const; std::is_const;` do not work either :( – Lol4t0 May 05 '12 at 17:31
  • @rhalbersma: Thanks for your answer, but what I really want to validate is that the *implementation* of `std::transform` conforms to its spec, not whether a particular argument `is_const`. From your explanation, it sounds like I might not be able to. – Jared Hoberock May 05 '12 at 18:16
3

In my answer, I'dd like to focus on problem, how to determine, if given iterator constant: std::is_const was mentioned, but it does not work in this case for me (gcc 4.7).

I assume, it is implemeted like

template <typename T>
struct is_const
{
    enum {value = false };
};

template <typename T>
struct is_const<const T>
{
    enum {value = true };

};

Now, one cannot check reference types with this struct, they would not match specialization, because const T would match int& const, that is constant reference to int, not const int&, that is reference to constant int, and first have no any sense.

Ok, but we are able to determine, if iterator is constant with the follwing struct:

template <typename Iterator>
struct is_iterator_constant
{
    typedef char yes_type;
    typedef struct{ char _[2];}  no_type;
    template <typename T>
    static no_type test(T&);

    template <typename T>
    static yes_type test(...);

    enum {value = sizeof(test<typename std::iterator_traits<Iterator>::value_type>(*Iterator())) == sizeof(yes_type) };

};
Lol4t0
  • 12,444
  • 4
  • 29
  • 65
  • 1
    This is a nice trick. You might want to post this answer to this related question http://stackoverflow.com/questions/2193399/how-do-i-require-const-iterator-semantics-in-a-template-function-signature (Q is already 2 years old, but OP is still active) UPDATE: see also http://stackoverflow.com/questions/5423246/how-to-detect-if-a-type-is-an-iterator-or-const-iterator for a slightly different solution. – TemplateRex May 05 '12 at 18:26
  • @rhalbersma, good links. fixed `std::iterator_traits` issue. :). I'll leave answer here for context. – Lol4t0 May 05 '12 at 20:16