microsoft / STL

MSVC's implementation of the C++ Standard Library.
Other
10.21k stars 1.51k forks source link

<expected> in permissive mode: error C7608: atomic constraint should be a constant expression #4657

Closed danielvandenberg95 closed 5 months ago

danielvandenberg95 commented 6 months ago

Describe the bug

In one of my projects, std::expected throws errors error C7608: atomic constraint should be a constant expression and error C2131: expression did not evaluate to a constant I think the latter is caused by the former.

In order to reproduce the issue, I inlined headers, removing them untill the issue disappeared. and discarding as much code as possible. I ended up with the following code in one file in my project:

// xloctime internal header

// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#ifndef _XLOCTIME_
#define _XLOCTIME_
#include <yvals_core.h>
#if _STL_COMPILER_PREPROCESSOR
#include <ctime>
#include <iterator>
#include <xlocnum>

#pragma pack(push, _CRT_PACKING)
#pragma warning(push, _STL_WARNING_LEVEL)
#pragma warning(disable : _STL_DISABLED_WARNINGS)
_STL_DISABLE_CLANG_WARNINGS
#pragma push_macro("new")
#undef new

_STD_BEGIN

_EXPORT_STD extern "C++" template <class _Elem, class _OutIt = ostreambuf_iterator<_Elem, char_traits<_Elem>>>
class time_put : public locale::facet { // facet for converting encoded times to text
public:
protected:
    __CLR_OR_THIS_CALL ~time_put() noexcept override {}

    virtual _OutIt __CLR_OR_THIS_CALL do_put(_OutIt _Dest, ios_base& _Iosbase, _Elem, const tm* _Pt, char _Specifier,
        char _Modifier = '\0') const {
        size_t _Count = 0;
        string _Str = "0";
        return _STD copy(&_Str[1], &_Str[_Count], _Dest); // Removing this line (replacing with return nullptr) causes the error to disappear.
    }

private:
    _Locinfo::_Timevec _Tnames; // locale-specific stuff for _Strftime
};

#if defined(_DLL_CPPLIB)

#if !defined(_CRTBLD) || defined(__FORCE_INSTANCE)
template class _CRTIMP2_PURE_IMPORT time_put<char, ostreambuf_iterator<char, char_traits<char>>>;
#endif // !defined(_CRTBLD) || defined(__FORCE_INSTANCE)

#endif // defined(_DLL_CPPLIB)
_STD_END

#pragma pop_macro("new")
_STL_RESTORE_CLANG_WARNINGS
#pragma warning(pop)
#pragma pack(pop)
#endif // _STL_COMPILER_PREPROCESSOR
#endif // _XLOCTIME_

#include <expected>
#include <string>

struct test {
    int x;
    int y;
};
using err = std::string;

std::expected<std::pair<int, int>, err> testFunction() {
    return std::pair<int, int>{ 5, 5 };
}

The full output I get while compiling this is:

Build started at 16:36...
1>------ Build started: Project: MyProject, Configuration: Debug x64 ------
1>test.cpp
1>C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\expected(572,18): error C7608: atomic constraint should be a constant expression
1>(compiling source file 'test.cpp')
1>C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\expected(572,18):
1>the template instantiation context (the oldest one first) is
1>  V:\Projects\MyProject\MyProject\test.cpp(77,97):
1>  see reference to class template instantiation 'std::time_put<char,std::ostreambuf_iterator<char,std::char_traits<char>>>' being compiled
1>  V:\Projects\MyProject\MyProject\test.cpp(35,39):
1>  while compiling class template member function '_OutIt std::time_put<char,_OutIt>::do_put(_OutIt,std::ios_base &,_Elem,const tm *,char,char) const'
1>        with
1>        [
1>            _OutIt=std::ostreambuf_iterator<char,std::char_traits<char>>,
1>            _Elem=char
1>        ]
1>  V:\Projects\MyProject\MyProject\test.cpp(66,21):
1>  see reference to function template instantiation '_OutIt std::copy<char*,_OutIt>(_InIt,_InIt,_OutIt)' being compiled
1>        with
1>        [
1>            _OutIt=std::ostreambuf_iterator<char,std::char_traits<char>>,
1>            _InIt=char *
1>        ]
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\xutility(4589,31):
1>  see reference to function template instantiation '_OutIt std::_Copy_unchecked<_InIt,_InIt,_OutIt>(_InIt,_Sent,_OutIt)' being compiled
1>        with
1>        [
1>            _OutIt=std::ostreambuf_iterator<char,std::char_traits<char>>,
1>            _InIt=char *,
1>            _Sent=char *
1>        ]
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\xutility(4559,23):
1>  see reference to alias template instantiation 'std::_Sent_copy_cat<_InIt,_Sent,_OutIt>' being compiled
1>        with
1>        [
1>            _InIt=char *,
1>            _Sent=char *,
1>            _OutIt=std::ostreambuf_iterator<char,std::char_traits<char>>
1>        ]
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\xutility(4446,28):
1>  see reference to variable template 'const bool _Iterators_are_contiguous<char *,std::ostreambuf_iterator<char,std::char_traits<char> > >' being compiled
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\xutility(4383,40):
1>  see reference to variable template 'const bool _Iterator_is_contiguous<std::ostreambuf_iterator<char,std::char_traits<char> > >' being compiled
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\xutility(4355,49):
1>  see reference to variable template 'bool contiguous_iterator<std::ostreambuf_iterator<char,std::char_traits<char> > >' being compiled
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\xutility(819,31):
1>  see reference to variable template 'bool random_access_iterator<std::ostreambuf_iterator<char,std::char_traits<char> > >' being compiled
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\xutility(807,34):
1>  see reference to variable template 'bool bidirectional_iterator<std::ostreambuf_iterator<char,std::char_traits<char> > >' being compiled
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\xutility(800,34):
1>  see reference to variable template 'bool forward_iterator<std::ostreambuf_iterator<char,std::char_traits<char> > >' being compiled
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\xutility(796,28):
1>  see reference to variable template 'bool input_iterator<std::ostreambuf_iterator<char,std::char_traits<char> > >' being compiled
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\xutility(785,26):
1>  see reference to variable template 'bool input_or_output_iterator<std::ostreambuf_iterator<char,std::char_traits<char> > >' being compiled
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\__msvc_iter_core.hpp(416,8):
1>  see reference to variable template 'bool weakly_incrementable<std::ostreambuf_iterator<char,std::char_traits<char> > >' being compiled
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\__msvc_iter_core.hpp(402,32):
1>  see reference to variable template 'bool movable<std::ostreambuf_iterator<char,std::char_traits<char> > >' being compiled
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\concepts(245,8):
1>  see reference to variable template 'bool swappable<std::ostreambuf_iterator<char,std::char_traits<char> > >' being compiled
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\concepts(124,26):
1>  see reference to variable template 'bool _Use_ADL_swap<std::ostreambuf_iterator<char,std::char_traits<char> > &,std::ostreambuf_iterator<char,std::char_traits<char> > &>' being compiled
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\expected(572,18):
1>  see reference to variable template 'const bool is_swappable_v<std::pair<int,int> >' being compiled
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\type_traits(2186,60):
1>  see reference to class template instantiation 'std::_Is_swappable<_Ty>' being compiled
1>        with
1>        [
1>            _Ty=std::pair<int,int>
1>        ]
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\type_traits(2147,102):
1>  see reference to class template instantiation 'std::_Is_swappable_with<_Ty &,_Ty &>' being compiled
1>        with
1>        [
1>            _Ty=std::pair<int,int>
1>        ]
1>  C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\type_traits(2142,21):
1>  see reference to variable template 'const bool conjunction_v<std::_Swappable_with_helper<std::pair<int,int> &,std::pair<int,int> &,void>,std::_Swappable_with_helper<std::pair<int,int> &,std::pair<int,int> &,void> >' being compiled
1>C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\expected(572,18): error C2131: expression did not evaluate to a constant
1>(compiling source file 'test.cpp')
1>C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\expected(572,18):
1>failure was caused by a read of an uninitialized symbol
1>C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\include\expected(572,18):
1>see usage of 'std::is_swappable_v<std::pair<int,int>>'
1>Done building project "MyProject.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
========== Build completed at 16:36 and took 01.019 seconds ==========

When enabling a precompiled header, this error goes away. I can not enable a precompiled header for this project though.

Command-line test case

I have not been able to reproduce it in command line. My attempt has been with: cl /permissive /MP /GS /TP /W4 /wd"4458" /Gy- /Zc:wchar_t /Zi /Gm- /Od /Ob0 /sdl /Zc:inline /fp:precise /D "_SILENCE_ALL_CXX20_DEPRECATION_WARNINGS" /D "_MBCS" /D "_SILENCE_CXX23_ALIGNED_STORAGE_DEPRECATION_WARNING" /D "_HEAPDEBUG=0" /D "DEBUG=1" /fp:except- /errorReport:prompt /WX- /Zc:forScope /RTC1 /Gd /MDd /std:c++latest /FC /EHsc /nologo /diagnostics:column /JMC .\repro.cpp as these are the closest to the command line options that my actual project is using.

Expected behavior

I would expect this to compile without issue.

STL version

Microsoft Visual Studio Professional 2022 Version 17.9.6

Additional context

I'm still trying to narrow down the issue, but seem to be stuck.

danielvandenberg95 commented 6 months ago

Got it reproduced:

CL.exe /c /Zi /JMC /nologo /W4 /WX- /diagnostics:column /sdl /MP24 /Od /Ob0 /D _SILENCE_ALL_CXX20_DEPRECATION_WARNINGS /D _SILENCE_CXX23_ALIGNED_STORAGE_DEPRECATION_WARNING /D _HEAPDEBUG=0 /D DEBUG=1 /D _MBCS /Gm- /EHsc /RTC1 /MDd /GS /Gy- /fp:precise /fp:except- /Zc:wchar_t /Zc:forScope /Zc:inline /std:c++latest /permissive /external:W4 /Gd /TP /wd4458 /FC /errorReport:prompt /MP /Zc:__cplusplus repro.cpp

#include <yvals_core.h>
#include <xloctime>

#include <expected>
#include <string>

struct test {
    int x;
    int y;
};
using err = std::string;

std::expected<std::pair<int, int>, err> testFunction() {
    return std::pair<int, int>{ 5, 5 };
}

The problem disappears when removing /permissive

frederick-vs-ja commented 6 months ago

Confirmed that /permissive /MD and /permissive /MDd can cause the problem. /permissive- (by default in C++20 and later modes), /MT, and /MTd are fine. Godbolt link.

Furtherly reduced example:

#include <xloctime>
//
#include <expected>
#include <string>

std::expected<std::pair<int, int>, std::string> testFunction() {
    return std::pair<int, int>{ 5, 5 };
}

At the first glance, I guess this is a compiler bug. But also note that <xloctime> is an internal header which shouldn't be directly included by user code.

~Can you reproduce the problem with only standard headers (directly) included?~

Edit: This example only includes necessary standard headers and reproduces the problem (Godbolt link).

#include <expected> // for std::expected
#include <locale>   // explicit instantiation definition(s) in <locale> can be problematic
#include <string>   // for std::string
#include <utility>  // for std::pair

std::expected<std::pair<int, int>, std::string> testFunction() {
    return std::pair<int, int>{5, 5};
}
cpplearner commented 6 months ago

Of course they can. Just replace <xloctime> with <locale>.

frederick-vs-ja commented 6 months ago

Roughly reduced to the following (Godbolt link):

#include <concepts>  // for std::ranges::swap and std::same_as
#include <expected>  // for std::expected
#include <string>    // for std::string and std::char_traits
#include <utility>   // for std::declval

template <class ElemT, class Traits>
struct my_outputter {};

template <class ElemT, class Outputter = ::my_outputter<ElemT, std::char_traits<ElemT>>>
struct my_putter {
    void do_put_like() const {
        // /permissive causes the ADL-found std::swap to be selected for my_outputter<char, std::char_traits<char>>
        static_assert(std::same_as<
            decltype(std::ranges::swap(std::declval<Outputter&>(), std::declval<Outputter&>())), void>);
    }
};

// explicit instantiation can be problematic
template struct my_putter<char, ::my_outputter<char, std::char_traits<char>>>; 

std::expected<int, std::string> testFunction() {
    return 42;
}

/MD and /MDd merely trigger the explicit instantiation via conditional compilation, so only the /permissive option is actually problematic IIUC.

In MSVC STL, std::copy needs to detect whether the iterator is contiguous, via std::contiguous_iterator since C++20. and std::contiguous_iterator eventually relies on std::swappable which uses std::ranges::swap. ~It seems that ADL in std::ranges::swap behaves differently in /permissive and /permissive- modes.~ #4363 may be related.

This is probably a bug of MSVC (or the nature of permissive modes). Workaround seems possible (Godbolt link).

Edit: Reported DevCom-10652420. The major reason of the bug doesn't seem to be the behavioral difference of ADL.