0

I am trying to write an algorithm that works on iterators (similar to the STL algorithms) however I need to write a specialization of the algorithm to act differently when the iterators point to complex values vs regular double values.

Here is a basic example:

#include <complex>
#include <iostream>
#include <vector>

using namespace std;

template <typename InputIt>
void DoSomething(InputIt first, InputIt last)
{
    cout << "Regular Double" << endl;

    for (; first != last; ++first)
    {
        cout << *first << endl;
    }
}

//// Specialize the template for containers holding complex values
//template <typename InputItToComplex>
//void DoSomething(InputItToComplex first, InputItToComplex last)
//{
//  cout << "Complex Double" << endl;
//
//  for (; first != last; ++first)
//  {
//      cout << *first << endl;
//  }
//}

int main()
{
    vector<double> values = { 1.5, 2.2, 3.1, 4.5, 5.1, 6.9, 7.1, 8.9 };

    // Call the regular template
    DoSomething(values.begin(), values.end());

    vector<complex<double>> cplx_values = { complex<double>{1.4, 2.1}, complex<double>{2.2, 3.5}, complex<double>{7.1, 9.1 } };

    // Need to call the complex specialized version of the template
    DoSomething(cplx_values.begin(), cplx_values.end());
}

How can I write the specialization so that it will automatically use the complex specialized version when I have a container of complex values? The commented out code above will obviously not work because it will just result in two ambiguous definitions.

tjwrona1992
  • 8,614
  • 8
  • 35
  • 98
  • @ChrisMM Not exactly because I'm still not sure how to apply it to iterators and still keep things generic – tjwrona1992 Jan 28 '20 at 17:46
  • It's the same, more or less `template<> DoSomething>::iterator>(…` See [here](https://godbolt.org/z/YnYUoz) – ChrisMM Jan 28 '20 at 17:48
  • Do you have access to C++17, and `if constexpr`, so you can do the distinction inside the function. – Jarod42 Jan 28 '20 at 17:50
  • Else, there is still SFINAE. – Jarod42 Jan 28 '20 at 17:52
  • 1
    @ChrisMM This limits the function to `std::vector` iterators. One of the goals of iterators is to abstract away the container and have algorithms that work on any container which can support it. Including user defined containers. – François Andrieux Jan 28 '20 at 17:55
  • @FrançoisAndrieux, true, I misread the question. Assumed always vector – ChrisMM Jan 28 '20 at 17:57
  • @Jarod42 yes, I have C++17 but i'm not very familiar with that feature. I'm fairly new to template metaprogramming. I will look into it thanks! – tjwrona1992 Jan 28 '20 at 18:19

2 Answers2

2

You can use SFINAE and std::iterator_traits to constrain the "specialized" template. You also need a helper to check if the value_type returned by the iterator trait is a specializartion of std::complex. That code is

template <class T, template <class...> class Template>
struct is_specialization : std::false_type {};

template <template <class...> class Template, class... Args>
struct is_specialization<Template<Args...>, Template> : std::true_type {};

And was written by Quentin here

Using that you would have

template <typename InputIt, 
          std::enable_if_t<!is_specialization<typename std::iterator_traits<InputIt>::value_type, std::complex>::value, bool> = true>
void DoSomething(InputIt first, InputIt last)
{
    cout << "Regular Double" << endl;

    for (; first != last; ++first)
    {
        cout << *first << endl;
    }
}

template <typename InputItToComplex, 
          std::enable_if_t<is_specialization<typename std::iterator_traits<InputItToComplex>::value_type, std::complex>::value, bool> = true>
void DoSomething(InputItToComplex first, InputItToComplex last)
{
    cout << "Complex Double" << endl;

    for (; first != last; ++first)
    {
        cout << *first << endl;
    }
}
tjwrona1992
  • 8,614
  • 8
  • 35
  • 98
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • This doesn't seem to compile for me. I get `is_specialization::value_type, std::complex>` "type name is not allowed" – tjwrona1992 Jan 28 '20 at 18:05
  • I'm using Visual Studio 2017 and have C++17 enabled – tjwrona1992 Jan 28 '20 at 18:06
  • @tjwrona1992 Sorry, I had a typo in the code. The new code should work. – NathanOliver Jan 28 '20 at 18:23
  • There was one more small error, but I figured it out and updated your answer. It is working now. Thanks! – tjwrona1992 Jan 28 '20 at 18:25
  • Still seems very complicated though for a pretty simple problem. Hopefully when concepts come around in C++20 that will make things like this easier. – tjwrona1992 Jan 28 '20 at 18:27
  • @tjwrona1992 Concepts will help to get rid of a lot of the verboseness. – NathanOliver Jan 28 '20 at 18:29
  • Would it be possible to provide some clarification on how `is_specialization` works? I am really struggling to wrap my head around the nested `template` within a `template`. – tjwrona1992 Jan 29 '20 at 21:10
  • @tjwrona1992 Really you should ask that as it's own question. I'm sure others have had that same question but never bother to ask. This would give us a good target to point to for others – NathanOliver Jan 29 '20 at 21:33
  • I've asked another question here https://stackoverflow.com/questions/59976065/how-do-nested-templates-get-resolved-in-c about how this works. It seems like it may take a bit to wrap my head around it lol – tjwrona1992 Jan 29 '20 at 22:27
  • @tjwrona1992 I think concepts solution is also ugly, but here it is: https://stackoverflow.com/a/51032862/700825 – NoSenseEtAl Apr 01 '21 at 01:17
1

As alternative to SFINAE (but still requires the traits), in C++17, you might use if constexpr (even if regular if would work in current snippet):

template <typename InputIt>
void DoSomething(InputIt first, InputIt last)
{
    if constexpr (is_specialization<typename std::iterator_traits<InputIt>::value_type,
                                    std::complex>::value) {
        std::cout << "Complex Double" << std::endl;
    } else {
        std::cout << "Regular Double" << std::endl;
    }
    for (; first != last; ++first) {
        std::cout << *first << std::endl;
    }
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • I still feel like there has to be a more syntactically elegant way to get this point across without going all the way to concepts lol – tjwrona1992 Jan 28 '20 at 22:26
  • From string messages, it seems you want to handle only `double` and `std::complex` whereas we handle iterators on `float`, `int`, `complex`, `std::string` (okay, it would just simplify the traits)... and if you restrict also container type, you could even get rid of template for `std::vector>::/*cont_*/iterator`. – Jarod42 Jan 28 '20 at 22:34