How does including gtest.h break template argument deduction for a std algorithm?
Asked Answered
J

1

6

I upgraded to the latest release of Google Test, and several of my tests no longer compiled. I've reduced it to this:

#include <gtest/gtest.h>

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>

int main () {
    const std::string foo = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    const auto uppers = std::count_if(foo.begin(), foo.end(), std::isupper);
    std::cout << "There are " << uppers << " capitals." << std::endl;
    return 0;
}

The Visual Studio 2019 (16.10.4) compiler with /std:c++latest complains:

1>Source.cpp(10,30): error C2672: 'std::count_if': no matching overloaded function found
1>Source.cpp(10,75): error C2780: 'conditional_t<std::_Is_from_primary<std::iterator_traits<remove_cv<remove_reference<_Ty2>::type>::type>>,std::incrementable_traits<remove_cv<remove_reference<_Ty2>::type>::type>,std::iterator_traits<remove_cv<remove_reference<_Ty2>::type>::type>>::difference_type std::count_if(_ExPo &&,const _FwdIt,const _FwdIt,_Pr) noexcept': expects 4 arguments - 3 provided
1>algorithm(570): message : see declaration of 'std::count_if'
1>Source.cpp(10,30): error C2783: 'conditional_t<std::_Is_from_primary<std::iterator_traits<remove_cv<remove_reference<_Ty>::type>::type>>,std::incrementable_traits<remove_cv<remove_reference<_Ty>::type>::type>,std::iterator_traits<remove_cv<remove_reference<_Ty>::type>::type>>::difference_type std::count_if(_InIt,_InIt,_Pr)': could not deduce template argument for '_Pr'
1>algorithm(553): message : see declaration of 'std::count_if'

If I comment out the inclusion of gtest.h, the code builds and executes correctly.

What could gtest.h be doing that messes up template argument deduction for a call that depends only on std-defined types and functions?

[Note, my question is not how to workaround the problem, but to understand the specific underlying cause. I have a workaround: Replace the std::isupper with a lambda.]

Jenisejenkel answered 23/7, 2021 at 14:21 Comment(3)
My guess, one of your headers is now including <locale>, which introduces template< class charT > bool isupper( charT ch, const locale& loc ) which means std::isupper is "now" overloaded and can't resolved. Does it work if you use const auto uppers = std::count_if(foo.begin(), foo.end(), []()(auto ch){ return std::isupper(static_cast<unsigned char>(ch));} ) instead?Plunge
Verify that by replacing <gtest/gtest.h> with <locale>. I think that's the answer, as you can see in CPPreference that std::isupper is overloaded in different header files.Typology
@NathanOliver: Brilliant deduction! I was thinking of std::isupper as a generic classifier--a sort foo function that returns bool. I completely forgot about the nonsense that <locale> brings in, and I hadn't expected Google Test to sneak it in. If you post an answer, the points are yours. Thanks also to @JDługosz for the verification tip!Jenisejenkel
P
3

It appears that <gtest/gtest.h> is now including <locale>, which introduces

template< class charT > bool isupper( charT ch, const locale& loc ) 

into the scope. That means that std::isupper now has two possible functions it could point to and without you specifying which one to use, you get an ambiguity which causes template argument deduction to fail.

If you do go the lambda route to fix this, make sure you cast the input to std::isupper to an unsigned char like

const auto uppers = std::count_if(foo.begin(), 
                                  foo.end(), 
                                  []()(auto ch){ return std::isupper(static_cast<unsigned char>(ch));} )
Plunge answered 23/7, 2021 at 17:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.