0

I'm writing a constructor and would like it to be defined for any type that implements [][] for integral arguments. In other words the type T must have T[] defined and the type returned when using [] on T itself also has [] defined, allowing for example, T[2][3] to be called.

I know how to check for the existence of an overloaded operator using the answer here which shows the approach for operator==, which can easily be extended to my case.

The issue however is that I'd like to check that the type of T[] also has operator[].

As aforementioned, I also need to check that the arguments accepted by the overloaded operator[] includes any integral type, so operator[](const T& i) would have some T which yields std::is_integral<T>::value being true.

Obviously, the purpose is for me to allow the interface to accept anything that behaves like a matrix with the standard means of accessing its elements.

user1997744
  • 411
  • 4
  • 16
  • 2
    Use detection idiom. I don’t recommend trying that if you’re uncomfortable with templates, as it might be pretty confusing at first. – Incomputable Jan 06 '18 at 23:23
  • @Incomputable I'm comfortable with using the stuff provided in type_traits and with the solution in the question I linked, but I think this task goes beyond my metaprogramming skills. Do you have a link to a good detection idiom example? – user1997744 Jan 06 '18 at 23:26
  • its almost the same as in linked question, if you want to go old way. The only difference is that you need two step check, as first might cause hard error. – Incomputable Jan 06 '18 at 23:27
  • @Incomputable Could I use the void_t trick here just to check if the expression is valid? – user1997744 Jan 06 '18 at 23:29
  • you have two expressions, namely those square brackets. Check first, then check second if first is true. – Incomputable Jan 06 '18 at 23:30
  • "*I know how to check for the existence of an overloaded operator*" - then you should have no trouble detecting the presence of `operator[]` on the type returned by `T::operator[]`. As for detecting if a given operator can be called with a given type, have a look at `std::is_invocable(_r)` – Remy Lebeau Jan 06 '18 at 23:33
  • No type supports `[][]`. Multi-dimensional array access involve sequential usage of `[]`. For example `a[2][3]` (assuming `a` is of class type) calls `a.operator[](2).operator[](3)`. This requires that `a` has an `operator[]()` which has a return type that also has an `operator[]()`. – Peter Jan 07 '18 at 00:21
  • 1
    @Peter This is exactly what I state in the first paragraph, that the type returned when using a first square bracket will give you a type that once again has that defined. – user1997744 Jan 07 '18 at 01:32

2 Answers2

2

You can sfinae e.g. on std::declval<const T>()[1][1]

template<class T, class = decltype(std::declval<const T&>()[1][1])>
void foo(const T& matrix){
    ...
}

or:

template<class T, decltype(void(std::declval<const T&>()[1][1]),true)=true>
void foo(const T& matrix){
  ...
}

which works better if you have multiple SFINAE selected foo overloads.

*) I'm on my mobile, so I haven't checked this with a compiler.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
MikeMB
  • 20,029
  • 9
  • 57
  • 102
  • I would use `std::declval()[1][1]`, as he's excplicitely asking about integer subscripts. Yours might also match pointer subscripts. – oisyn Jan 06 '18 at 23:54
  • Note that this technique has problems when used for selecting between overloads. You can change `class=enable_if_t` to `enable_if_t =true` to fix that. – Yakk - Adam Nevraumont Jan 07 '18 at 01:10
  • @Yakk: Can you elaborate? There is no enable_if here. – MikeMB Jan 07 '18 at 01:15
  • And yes, it is a basic solution. I'm reluctant to provide a more flexible, but also more complicated solution unless I know exactly what the requirements are. – MikeMB Jan 07 '18 at 01:18
  • @mike oops, misread `decltype(std::declval()[1][1])` to have an enable if (hoe did I do that you may ask? I am that awesome, which is to say I suck). Try `decltype(std::declval()[1][1],void(),true)=true` to get multiple-override friendly version of your `foo`. – Yakk - Adam Nevraumont Jan 07 '18 at 01:18
  • Why use const T instead of T? – user1997744 Jan 07 '18 at 01:19
  • @user1997744: Because in my example, I take T by const ref and want to check for the existence of a const qualified operator[]. If you don't care about const/non-const, you can just check for T. – MikeMB Jan 07 '18 at 01:44
  • @Yakk: Thanks, I guess the void is there to guard against return types that overload the comma operator? – MikeMB Jan 07 '18 at 01:45
  • Correct. It could wrap the declval instead. – Yakk - Adam Nevraumont Jan 07 '18 at 02:56
  • @Yakk: I'll update the answer with your suggestions once I'm in front of a proper keyboard (which will be in 2-3 days), but if you want to post your solution in the mean time, go ahead and I'll remove mine. – MikeMB Jan 07 '18 at 03:05
  • @Yakk Could you elaborate why you have void() and why at the end it's assigned to true? – user1997744 Jan 07 '18 at 09:51
  • @user it isms non-type template argument which, only wjen the sfinae passes, is of type `bool`. So it needs a value (true or false does not matter). By having a conditional template argument instead of conditional template argu,ent default, it lets you have two overloads of foo tjat have different sfinae tests (with the default technique you end up with two `templatevoid foo()` templates, which is an error). The `void` is defence against someone overriding `operator,`, because we want to evaluate the `declval` based expression, bit the type we want is bool. – Yakk - Adam Nevraumont Jan 07 '18 at 12:22
  • @Yakk So the first checks if the expression is well-formed, the void takes care of overloaded comma and then we get decltype of a boolean giving us a bool type (unnamed) which gets set to true. But how does void() handle overloaded comma? – user1997744 Jan 07 '18 at 12:37
  • @user you cannot overload on void. – Yakk - Adam Nevraumont Jan 07 '18 at 13:16
2

Here are two ways of doing it. The first one does not use any external library and uses the same idea as the linked answer you have posted. The second one uses functionality from the boost.hana library.

#include <iostream>
#include <type_traits>
#include <vector>
#include <boost/hana/type.hpp>

// SFINAE trick, not using any external libraries
template <class X>
class check {

    // this overload will be selected whenever X[][] is defined
    template <class T>
    static auto call(T*) -> decltype(std::declval<T>()[1][1], std::true_type{});

    // and this when X[][] is not defined
    template <class T>
    static std::false_type call(...);

public:

    using type = decltype (call<X>(0));
};

// using boost.hana library
template <class X>
class hana_check {

    static auto call() {
        auto test = boost::hana::is_valid([](auto&& x) -> decltype (x[1][1]) {});
        return decltype(test(std::declval<X>())){};
    }

public:

    using type = decltype (call());
};

using test_type1 = check<int>::type; // false_type
using test_type2 = check<std::vector<std::vector<int>>>::type; // true_type
using test_type3 = hana_check<double>::type; // false_type
using test_type4 = hana_check<std::vector<std::vector<double>>>::type; // true_type

int main() {

    std::cout << test_type1::value << std::endl;
    std::cout << test_type2::value << std::endl;
    std::cout << test_type3::value << std::endl;
    std::cout << test_type4::value << std::endl;
}
linuxfever
  • 3,763
  • 2
  • 19
  • 43