2

For reasons I want to be able to do this;

vector<int> p = {1, 2};
vector<vector<int>> q = {p, {0, 1}};

auto t = test(p);
auto u = test(q); // Fails with below implementation

Where notably test is templated to accept custom classes which might or might not be iterable for one or two (for now) dimensions. I'm trying to determine what to do by checking if whatever was given has a size function;

template<typename T> struct hasSize {
    template<typename U, size_t(U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::size>*);
    template<typename U> static int  Test(...);

    static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};

template<typename iterable> int test(const iterable &x, std::false_type) {
    return (int) x;
}

template<typename iterable> int test(const iterable &x, std:: true_type) {
    int total = 0;

    for(auto &each : x)
        total += test(each,
            std::integral_constant<bool, hasSize<decltype(each)>::value>());

    return total;
}

template<typename iterable> int test(const iterable &view) {
    return test(view, std::true_type());
}

I based hasSize on the answers given here after giving up on this answer, since that seemed to be only applicable to member variables, not functions. I also tried a modified version of has_const_reference_op given in first discussion, but this has the same problem.

The error given suggests SNIFAE is not applied a second time;

error C2440: 'type cast':
    cannot convert from 'const std::vector<int, std::allocator<_Ty>>' to 'int'
note: No user-defined-conversion operator available that can perform this conversion,
    or the operator cannot be called
note: see reference to function template instantiation
    'int test<iterable>(const iterable &, std::false_type)' being compiled
with iterable = std::vector<int,std::allocator<int>>

But I have no idea why.

Community
  • 1
  • 1
Meeuwisse
  • 157
  • 6
  • You probably want `hasSize::type>::value>()` – Piotr Skotnicki Apr 29 '16 at 14:55
  • Maybe beside the point for you, but I think you will need to do more than just check for existence of the `size()` function to determine if a type is composed of nested containers. For example, the string class has a `size()` function. – bfair Apr 29 '16 at 14:57

2 Answers2

1

The reason why it fails is that the auto&-typed variable is actually of type const std::vector<int>& with iterable of type const vector<vector<int>>&, and so when queried with decltype -- it yields a reference type that fails the SFINAE check for the size member function existance. So instead of using decltype, just read the value_type from iterable:

total += test(each, std::integral_constant<bool,
                       hasSize<typename iterable::value_type>::value 
//                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
                    >());

or remove referenceness/constness from the type produced by decltype:

total += test(each, std::integral_constant<bool, 
                       hasSize<typename std::decay<decltype(each)>::type>::value
//                                      ~~~~~~~~~^
                    >());
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • Works with very minimal changes, but I'm not sure why a variable of type `const std::vector&` is considered not to have a size member. Could you elaborate? – Meeuwisse Apr 29 '16 at 21:38
  • @Meeuwisse a reference type is a different type than the type a reference points to, jusr like a pointer, does a pointer have member funtions? – Piotr Skotnicki Apr 29 '16 at 22:01
0

First of all, this isn't the right thing for the general test():

template<typename iterable> int test(const iterable &view) {
    return test(view, std::true_type());
}

Since in general test isn't iterable - that's what we need to test for! So instead, we're going to forward this to a series of three functions in a namespace so that we can take advantage of ADL to find everything that we need to find:

namespace adl {
    struct helper {};

    template<typename iterable>
    int test(helper, const iterable &x, std::false_type) {
        return (int) x;
    }

    template<typename iterable>
    int test(helper, const iterable &x, std:: true_type) {
        int total = 0;

        for(auto &each : x) {
            // this calls the general one
            total += test(helper{}, each);
        }

        return total;
    }

    template <typename iterable>
    int test(helper, const iterable& x) {
        return test(helper{}, x, std::integral_constant<bool, hasSize<iterable>::value>{});
    }
}

template<typename iterable>
int test(const iterable &view) {
    return test(adl::helper{}, view);
}

We need ADL so that each of the functions can find each other.


Note that it's better to write type traits that yield types instead of just values. Had we written something like:

template <class T, class = void>
struct hasSize : std::false_type { };

template <class T>
struct hasSize<T, void_t<decltype(std::declval<T const&>().size())>>
: std::true_type { };

then our tester overload could be much shorter:

template <typename iterable>
int test(helper, const iterable& x) {
    return test(helper{}, x, hasSize<iterable>{});
}

This may not work on VS2013, but you could still add a type typedef to your hasSize.

Barry
  • 286,269
  • 29
  • 621
  • 977