Repository: conan-io/conan
Pull request: Add cppstd as a subsetting of compiler
Changelog: Feature: Add `compiler.cppstd` setting (mark `cppstd` as deprecated)
Docs: https://github.com/conan-io/docs/pull/1266

This PR includes changes from PR #4986 and #4942 (really short ones)

----

Some hints about the new/old behavior:
 - It keeps previous behavior: all use cases (tests) should be summarized here https://github.com/conan-io/conan/pull/4986, 
 - The default value (or `None`) for `compiler.cppstd` generates the same ID as it was generating for `cppstd`.
 - If the user specifies `cppstd` and `compiler.cppstd`, Conan will raise. Yes, even if those have the same values.
 - Package ID for non-default values generates different ID for `cppstd` and `compiler.cppstd`
 - If no value is provided to `compiler.cppstd` then the default value will be assigned to it.

Summarizing:
```
package_id("cppstd=default") == package_id("cppstd=None")
package_id("cppstd=default") == package_id("compiler.cppstd=default")
package_id("compiler.cppstd=default") == package_id("compiler.cppstd=None")
package_id("cppstd=20") != package_id("compiler.cppstd=20")
```

----

closes #4873 

@TAGS: slow

Problem statement:
cppstd as a subsetting
We considered better to have the cppstd as a subsetting of every compiler:

- If you don't specify the -s compiler.cppstd=XXX it won't change anything, because will come with a None default value.
- We won't break anything, the global setting will be kept, but deprecated (comment at settings.yml and docs).
- So any recipe could receive the subsetting and compile with a different version of the language automatically.
- The reasons to do a first level setting was to not generate new binary IDS for C libraries that doesn't remove the subsetting. We consider more important to be "injectable" at any recipe.
- Conan will fail if both are specified.
- Conan will try internally to manage the value of the new one, copying from the old one if possible (to ease the code and future deprecation of the global setting)
- Each compiler will have their own values, it has a lot of sense.

Hints from issue discussion:
Q: Although each compiler will have its own flags to set the standard, I wonder if we should add those different names in our `settings.yml` or it is better to set `None, 98, 11, 14, 17, 20` for all the compilers and make the translation inside Conan code.

We don't have to introduce the flag name in the settings. But values that make sense for the compiler. For instance, the "gnu" ones for VS doesn't make sense. So yes, translation still will happen. I would use the same names as today, but removing the non-sense ones, unless you find that maybe for vs other values makes more sense.
I was thinking about the way CMake handles it through the [`CXX_STANDARD` variable](https://cmake.org/cmake/help/v3.14/prop_tgt/CXX_STANDARD.html), they use the same in the `CMakeLists.txt` for all the generators. And then, CMake has a different flag, `CXX_EXTENSIONS` to handle compiler specific extensions.

Let's list all the valid values for each generator and see how it looks like.

Commit message: remove comment

Patch to review:
diff --git a/conans/client/conan_api.py b/conans/client/conan_api.py
index abb9fbbe46d..0ea1b833ae7 100644
--- a/conans/client/conan_api.py
+++ b/conans/client/conan_api.py
@@ -1173,7 +1173,7 @@ def get_graph_info(profile_names, settings, options, env, cwd, install_folder, c
                                  % install_folder)
         graph_info = None
 
-    if profile_names or settings or options or profile_names or env or not graph_info:
+    if profile_names or settings or options or env or not graph_info:
         if graph_info:
             # FIXME: Convert to Exception in Conan 2.0
             output.warn("Settings, options, env or profile specified. "
diff --git a/conans/client/conf/__init__.py b/conans/client/conf/__init__.py
index 50e200b16ff..ae6909d52e3 100644
--- a/conans/client/conf/__init__.py
+++ b/conans/client/conf/__init__.py
@@ -62,6 +62,7 @@
         libcxx: [libstdc++, libstdc++11]
         threads: [None, posix, win32] #  Windows MinGW
         exception: [None, dwarf2, sjlj, seh] # Windows MinGW
+        cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
     Visual Studio:
         runtime: [MD, MT, MTd, MDd]
         version: ["8", "9", "10", "11", "12", "14", "15", "16"]
@@ -69,17 +70,19 @@
                   v140, v140_xp, v140_clang_c2, LLVM-vs2012, LLVM-vs2012_xp,
                   LLVM-vs2013, LLVM-vs2013_xp, LLVM-vs2014, LLVM-vs2014_xp,
                   LLVM-vs2017, LLVM-vs2017_xp, v141, v141_xp, v141_clang_c2, v142]
+        cppstd: [None, 14, 17, 20]
     clang:
         version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0",
-                  "5.0", "6.0", "7.0",
-                  "8"]
+                  "5.0", "6.0", "7.0", "8"]
         libcxx: [libstdc++, libstdc++11, libc++]
+        cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
     apple-clang:
         version: ["5.0", "5.1", "6.0", "6.1", "7.0", "7.3", "8.0", "8.1", "9.0", "9.1", "10.0"]
         libcxx: [libstdc++, libc++]
+        cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
 
 build_type: [None, Debug, Release, RelWithDebInfo, MinSizeRel]
-cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
+cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]  # Deprecated, use compiler.cppstd
 """
 
 default_client_conf = """
diff --git a/conans/client/migrations_settings.py b/conans/client/migrations_settings.py
index b96bdbc186a..78c197cb077 100644
--- a/conans/client/migrations_settings.py
+++ b/conans/client/migrations_settings.py
@@ -288,3 +288,75 @@
 settings_1_14_2 = settings_1_14_1
 settings_1_14_3 = settings_1_14_2
 settings_1_14_4 = settings_1_14_3
+
+settings_1_15_0 = """
+# Only for cross building, 'os_build/arch_build' is the system that runs Conan
+os_build: [Windows, WindowsStore, Linux, Macos, FreeBSD, SunOS]
+arch_build: [x86, x86_64, ppc32, ppc64le, ppc64, armv5el, armv5hf, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr, s390, s390x]
+
+# Only for building cross compilation tools, 'os_target/arch_target' is the system for
+# which the tools generate code
+os_target: [Windows, Linux, Macos, Android, iOS, watchOS, tvOS, FreeBSD, SunOS, Arduino]
+arch_target: [x86, x86_64, ppc32, ppc64le, ppc64, armv5el, armv5hf, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr, s390, s390x]
+
+# Rest of the settings are "host" settings:
+# - For native building/cross building: Where the library/program will run.
+# - For building cross compilation tools: Where the cross compiler will run.
+os:
+    Windows:
+        subsystem: [None, cygwin, msys, msys2, wsl]
+    WindowsStore:
+        version: ["8.1", "10.0"]
+    Linux:
+    Macos:
+        version: [None, "10.6", "10.7", "10.8", "10.9", "10.10", "10.11", "10.12", "10.13", "10.14"]
+    Android:
+        api_level: ANY
+    iOS:
+        version: ["7.0", "7.1", "8.0", "8.1", "8.2", "8.3", "9.0", "9.1", "9.2", "9.3", "10.0", "10.1", "10.2", "10.3", "11.0", "11.1", "11.2", "11.3", "11.4", "12.0", "12.1"]
+    watchOS:
+        version: ["4.0", "4.1", "4.2", "4.3", "5.0", "5.1"]
+    tvOS:
+        version: ["11.0", "11.1", "11.2", "11.3", "11.4", "12.0", "12.1"]
+    FreeBSD:
+    SunOS:
+    Arduino:
+        board: ANY
+arch: [x86, x86_64, ppc32, ppc64le, ppc64, armv5el, armv5hf, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr, s390, s390x]
+compiler:
+    sun-cc:
+        version: ["5.10", "5.11", "5.12", "5.13", "5.14"]
+        threads: [None, posix]
+        libcxx: [libCstd, libstdcxx, libstlport, libstdc++]
+    gcc:
+        version: ["4.1", "4.4", "4.5", "4.6", "4.7", "4.8", "4.9",
+                  "5", "5.1", "5.2", "5.3", "5.4", "5.5",
+                  "6", "6.1", "6.2", "6.3", "6.4",
+                  "7", "7.1", "7.2", "7.3",
+                  "8", "8.1", "8.2"]
+        libcxx: [libstdc++, libstdc++11]
+        threads: [None, posix, win32] #  Windows MinGW
+        exception: [None, dwarf2, sjlj, seh] # Windows MinGW
+        cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
+    Visual Studio:
+        runtime: [MD, MT, MTd, MDd]
+        version: ["8", "9", "10", "11", "12", "14", "15", "16"]
+        toolset: [None, v90, v100, v110, v110_xp, v120, v120_xp,
+                  v140, v140_xp, v140_clang_c2, LLVM-vs2012, LLVM-vs2012_xp,
+                  LLVM-vs2013, LLVM-vs2013_xp, LLVM-vs2014, LLVM-vs2014_xp,
+                  LLVM-vs2017, LLVM-vs2017_xp, v141, v141_xp, v141_clang_c2, v142]
+        cppstd: [None, 14, 17, 20]
+    clang:
+        version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0",
+                  "5.0", "6.0", "7.0",
+                  "8"]
+        libcxx: [libstdc++, libstdc++11, libc++]
+        cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
+    apple-clang:
+        version: ["5.0", "5.1", "6.0", "6.1", "7.0", "7.3", "8.0", "8.1", "9.0", "9.1", "10.0"]
+        libcxx: [libstdc++, libc++]
+        cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
+
+build_type: [None, Debug, Release, RelWithDebInfo, MinSizeRel]
+cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]  # Deprecated, use compiler.cppstd
+"""
\ No newline at end of file
diff --git a/conans/client/settings_preprocessor.py b/conans/client/settings_preprocessor.py
index 73b0dcc3c81..3b9a1815cda 100644
--- a/conans/client/settings_preprocessor.py
+++ b/conans/client/settings_preprocessor.py
@@ -1,30 +1,73 @@
+import warnings
+
+from conans.client.build.cppstd_flags import cppstd_default
 from conans.client.build.cppstd_flags import cppstd_flag
 from conans.errors import ConanException
 from conans.util.log import logger
 
 
 def preprocess(settings):
-    fill_runtime(settings)
-    check_cppstd(settings)
+    _fill_runtime(settings)
+    _fill_compiler_cppstd(settings)
+    _check_cppstd(settings)
+
+
+def _fill_compiler_cppstd(settings):
+    compiler = settings.get_safe("compiler")
+    compiler_version = settings.get_safe("compiler.version")
+    cppstd = settings.get_safe("cppstd")
+    compiler_cppstd = settings.get_safe("compiler.cppstd")
+
+    # Assign the explicit default value to compiler.cppstd (only if not given cppstd)
+    if not cppstd and not compiler_cppstd and compiler and compiler_version:
+        default_cppstd = cppstd_default(compiler, compiler_version)
+        if default_cppstd:
+            try:
+                settings.compiler.cppstd = default_cppstd
+            except Exception:
+                # Settings structure does not have cppstd
+                pass
 
 
-def check_cppstd(settings):
+def _check_cppstd(settings):
     compiler = settings.get_safe("compiler")
     compiler_version = settings.get_safe("compiler.version")
     cppstd = settings.get_safe("cppstd")
-    if not cppstd or compiler not in ("gcc", "clang", "apple-clang", "Visual Studio"):
+    compiler_cppstd = settings.get_safe("compiler.cppstd")
+
+    if not cppstd and not compiler_cppstd:
         return
-    cpp_values = settings.cppstd.values_range
-    available = [v for v in cpp_values if cppstd_flag(compiler, compiler_version, v)]
-    if str(cppstd) not in available:
-        raise ConanException("The specified 'cppstd=%s' is not available "
-                             "for '%s %s'. Possible values are %s'" % (cppstd,
-                                                                       compiler,
-                                                                       compiler_version,
-                                                                       available))
+
+    # Checks: one or the other, but not both
+    if cppstd and compiler_cppstd:
+        raise ConanException("Do not use settings 'compiler.cppstd' together with 'cppstd'."
+                             " Use only the former one.")
+
+    if cppstd:
+        warnings.warn("Setting 'cppstd' is deprecated in favor of 'compiler.cppstd'")
+
+    if compiler not in ("gcc", "clang", "apple-clang", "Visual Studio"):
+        return
+
+    # Check that we have a flag available for that value of the C++ Standard
+    def check_flag_available(values_range, value, setting_id):
+        available = [v for v in values_range if cppstd_flag(compiler, compiler_version, v)]
+        if str(value) not in available:
+            raise ConanException("The specified '%s=%s' is not available "
+                                 "for '%s %s'. Possible values are %s'" % (setting_id,
+                                                                           value,
+                                                                           compiler,
+                                                                           compiler_version,
+                                                                           available))
+
+    if cppstd:
+        check_flag_available(settings.cppstd.values_range, cppstd, "cppstd")
+    else:
+        check_flag_available(settings.compiler.cppstd.values_range,
+                             compiler_cppstd, "compiler.cppstd")
 
 
-def fill_runtime(settings):
+def _fill_runtime(settings):
     try:
         if settings.compiler == "Visual Studio":
             if settings.get_safe("compiler.runtime") is None:
diff --git a/conans/client/tools/system_pm.py b/conans/client/tools/system_pm.py
index d3599758427..7c66f0ceece 100644
--- a/conans/client/tools/system_pm.py
+++ b/conans/client/tools/system_pm.py
@@ -148,8 +148,8 @@ def update(self):
         pass
 
     def install(self, package_name):
-        self._output.warn("Only available for linux with apt-get, yum, or pacman or OSX with brew or "
-                            "FreeBSD with pkg or Solaris with pkgutil")
+        self._output.warn("Only available for linux with apt-get, yum, or pacman or OSX with brew or"
+                          " FreeBSD with pkg or Solaris with pkgutil")
 
     def installed(self, package_name):
         return False
diff --git a/conans/model/conan_file.py b/conans/model/conan_file.py
index 848ea67b279..92c876d662c 100644
--- a/conans/model/conan_file.py
+++ b/conans/model/conan_file.py
@@ -3,6 +3,7 @@
 
 from conans.client import tools
 from conans.client.output import Color, ScopedOutput
+from conans.client.settings_preprocessor import preprocess
 from conans.client.tools.env import environment_append, no_op, pythonpath
 from conans.client.tools.oss import OSInfo
 from conans.errors import ConanException
@@ -132,6 +133,12 @@ def initialize(self, settings, env):
         self.options = create_options(self)
         self.requires = create_requirements(self)
         self.settings = create_settings(self, settings)
+
+        try:
+            preprocess(self.settings)
+        except ConanException as e:
+            raise ConanException("Package '{}': {}".format(self.display_name, e))
+
         try:
             if self.settings.os_build and self.settings.os:
                 self.output.writeln("*"*60, front=Color.BRIGHT_RED)
@@ -145,6 +152,10 @@ def initialize(self, settings, env):
         except ConanException:
             pass
 
+        if 'cppstd' in self.settings.fields:
+            self.output.writeln("Setting 'cppstd' is deprecated in favor of 'compiler.cppstd',"
+                                " please update your recipe.", front=Color.BRIGHT_RED)
+
         # needed variables to pack the project
         self.cpp_info = None  # Will be initialized at processing time
         self.deps_cpp_info = DepsCppInfo()
diff --git a/conans/model/info.py b/conans/model/info.py
index 475034bfa47..4b7ed51d2db 100644
--- a/conans/model/info.py
+++ b/conans/model/info.py
@@ -231,6 +231,9 @@ def serialize(self):
 
 class ConanInfo(object):
 
+    def __init__(self):
+        self._adjust_settings_for_std = lambda u: u
+
     def copy(self):
         """ Useful for build_id implementation
         """
@@ -271,6 +274,9 @@ def loads(text):
         result = ConanInfo()
         result.settings = Values.loads(parser.settings)
         result.full_settings = Values.loads(parser.full_settings)
+        # TODO: Apply here the _fill_compiler_cppstd for settings and full_settings for legacy
+        # TODO: packages, but there is no way to know the std_matching mode if ID is requested?
+
         result.options = OptionsValues.loads(parser.options)
         result.full_options = OptionsValues.loads(parser.full_options)
         result.full_requires = _PackageReferenceList.loads(parser.full_requires)
@@ -336,7 +342,7 @@ def package_id(self):
         options and settings
         """
         result = []
-        result.append(self.settings.sha)
+        result.append(self._settings_sha())
         # Only are valid requires for OPtions those Non-Dev who are still in requires
         self.options.filter_used(self.requires.pkg_names)
         result.append(self.options.sha)
@@ -398,14 +404,31 @@ def default_std_matching(self):
         same as specifying None, packages are the same
         """
 
-        if self.full_settings.cppstd and \
-                self.full_settings.compiler and \
-                self.full_settings.compiler.version:
+        if self.full_settings.compiler and \
+            self.full_settings.compiler.version:
             default = cppstd_default(str(self.full_settings.compiler),
                                      str(self.full_settings.compiler.version))
-            if default == str(self.full_settings.cppstd):
+
+            if str(self.full_settings.cppstd) == default:
                 self.settings.cppstd = None
 
+            if str(self.full_settings.compiler.cppstd) == default:
+                def remove_cppstd(settings):
+                    try:
+                        settings.compiler.cppstd = None  # It's the default, assign None
+                    except AttributeError:
+                        # Settings can be different at the moment of executing this function
+                        pass
+                    finally:
+                        return settings
+                self._adjust_settings_for_std = remove_cppstd
+
     def default_std_non_matching(self):
         if self.full_settings.cppstd:
             self.settings.cppstd = self.full_settings.cppstd
+        self._adjust_settings_for_std = lambda u: u  # Do nothing
+
+    def _settings_sha(self):
+        settings = self.settings.copy()
+        settings = self._adjust_settings_for_std(settings)
+        return settings.sha
diff --git a/conans/test/functional/build_helpers/cmake_flags_test.py b/conans/test/functional/build_helpers/cmake_flags_test.py
index 4d470d026ac..f6b466b1742 100644
--- a/conans/test/functional/build_helpers/cmake_flags_test.py
+++ b/conans/test/functional/build_helpers/cmake_flags_test.py
@@ -6,9 +6,9 @@
 from nose.plugins.attrib import attr
 from parameterized.parameterized import parameterized
 
-from conans import load
 from conans.client.build.cmake import CMake
 from conans.model.version import Version
+from conans.test.utils.deprecation import catch_deprecation_warning
 from conans.test.utils.tools import TestClient
 
 conanfile_py = """
@@ -322,20 +322,26 @@ def build(self):
 """})
 
         if platform.system() != "Windows":
-            client.run("install . --install-folder=build -s cppstd=gnu98")
-            client.run("build . --build-folder=build", assert_error=True)
+            with catch_deprecation_warning(self, n=2):
+                client.run("install . --install-folder=build -s cppstd=gnu98")
+            with catch_deprecation_warning(self):
+                client.run("build . --build-folder=build", assert_error=True)
             self.assertIn("Error in build()", client.out)
 
             # Now specify c++14
-            client.run("install . --install-folder=build -s cppstd=gnu14")
-            client.run("build . --build-folder=build")
+            with catch_deprecation_warning(self, n=2):
+                client.run("install . --install-folder=build -s cppstd=gnu14")
+            with catch_deprecation_warning(self):
+                client.run("build . --build-folder=build")
             self.assertIn("CPP STANDARD: 14 WITH EXTENSIONS ON", client.out)
             libname = "libmylib.a" if platform.system() != "Windows" else "mylib.lib"
             libpath = os.path.join(client.current_folder, "build", "lib", libname)
             self.assertTrue(os.path.exists(libpath))
 
-        client.run("install . --install-folder=build -s cppstd=14")
-        client.run("build . --build-folder=build")
+        with catch_deprecation_warning(self, n=2):
+            client.run("install . --install-folder=build -s cppstd=14")
+        with catch_deprecation_warning(self):
+            client.run("build . --build-folder=build")
         self.assertIn("CPP STANDARD: 14 WITH EXTENSIONS OFF", client.out)
         self.assertNotIn("Conan setting CXX_FLAGS flags", client.out)
         libname = "libmylib.a" if platform.system() != "Windows" else "mylib.lib"
@@ -375,15 +381,17 @@ def conan_set_std_branch():
             cmake_version = CMake.get_version()
             return cmake_version < Version("3.12")
 
-        client.run("create . user/channel -s cppstd=gnu20 -s compiler=gcc -s compiler.version=8 "
-                   "-s compiler.libcxx=libstdc++11")
+        with catch_deprecation_warning(self, n=2):
+            client.run("create . user/channel -s cppstd=gnu20 -s compiler=gcc -s compiler.version=8 "
+                       "-s compiler.libcxx=libstdc++11")
         if conan_set_std_branch():
             self.assertIn("Conan setting CXX_FLAGS flags: -std=gnu++2a", client.out)
         else:
             self.assertIn("Conan setting CPP STANDARD: 20 WITH EXTENSIONS ON", client.out)
 
-        client.run("create . user/channel -s cppstd=20 -s compiler=gcc -s compiler.version=8 "
-                   "-s compiler.libcxx=libstdc++11")
+        with catch_deprecation_warning(self, n=2):
+            client.run("create . user/channel -s cppstd=20 -s compiler=gcc -s compiler.version=8 "
+                       "-s compiler.libcxx=libstdc++11")
         if conan_set_std_branch():
             self.assertIn("Conan setting CXX_FLAGS flags: -std=c++2a", client.out)
         else:
diff --git a/conans/test/functional/build_helpers/msbuild_test.py b/conans/test/functional/build_helpers/msbuild_test.py
index 8a0923770b6..97c1457efb3 100644
--- a/conans/test/functional/build_helpers/msbuild_test.py
+++ b/conans/test/functional/build_helpers/msbuild_test.py
@@ -10,6 +10,7 @@
 from conans.paths import CONANFILE
 from conans.test.utils.tools import TestClient
 from conans.test.utils.visual_project_files import get_vs_project_files
+from conans.test.utils.deprecation import catch_deprecation_warning
 
 
 class MSBuildTest(unittest.TestCase):
@@ -41,10 +42,12 @@ def package(self):
         files[CONANFILE] = conan_build_vs
 
         client.save(files)
-        client.run('create . Hello/1.2.1@lasote/stable -s cppstd=11 -s '
-                   'compiler="Visual Studio" -s compiler.version=14', assert_error=True)
-        client.run('create . Hello/1.2.1@lasote/stable -s cppstd=17 '
-                   '-s compiler="Visual Studio" -s compiler.version=14')
+        with catch_deprecation_warning(self):
+            client.run('create . Hello/1.2.1@lasote/stable -s cppstd=11 -s '
+                       'compiler="Visual Studio" -s compiler.version=14', assert_error=True)
+        with catch_deprecation_warning(self, n=2):
+            client.run('create . Hello/1.2.1@lasote/stable -s cppstd=17 '
+                       '-s compiler="Visual Studio" -s compiler.version=14')
         self.assertIn("Packaged 1 '.exe' file: MyProject.exe", client.out)
 
         files = get_vs_project_files()
diff --git a/conans/test/functional/configuration/profile_test.py b/conans/test/functional/configuration/profile_test.py
index 44bf0bea550..fb0681a7062 100644
--- a/conans/test/functional/configuration/profile_test.py
+++ b/conans/test/functional/configuration/profile_test.py
@@ -551,6 +551,7 @@ def profile_crazy_inheritance_test(self):
                              [settings]
                              arch=x86_64
                              compiler=Visual Studio
+                             compiler.cppstd=14
                              compiler.runtime=MD
                              compiler.version=15
                              os=Windows"""), self.client.out)
diff --git a/conans/test/functional/cppstd/compiler_cppstd_test.py b/conans/test/functional/cppstd/compiler_cppstd_test.py
new file mode 100644
index 00000000000..2da8acbb502
--- /dev/null
+++ b/conans/test/functional/cppstd/compiler_cppstd_test.py
@@ -0,0 +1,164 @@
+# coding=utf-8
+
+import os
+import textwrap
+import unittest
+
+from parameterized import parameterized
+from parameterized.parameterized import parameterized_class
+
+from conans.client.tools import environment_append, save
+from conans.test.utils.deprecation import catch_deprecation_warning
+from conans.test.utils.test_files import temp_folder
+from conans.test.utils.tools import TestClient
+
+
+@parameterized_class([{"recipe_cppstd": True}, {"recipe_cppstd": False}, ])
+class SettingsCppStdScopedPackageTests(unittest.TestCase):
+    # Validation of scoped settings is delayed until graph computation, a conanfile can
+    #   declare a different set of settings, so we should wait until then to validate it.
+
+    default_profile = textwrap.dedent("""
+        [settings]
+        os=Linux
+        arch=x86
+        compiler=gcc
+        compiler.version=7
+        compiler.libcxx=libstdc++11
+    """)
+
+    def run(self, *args, **kwargs):
+        default_profile_path = os.path.join(temp_folder(), "default.profile")
+        save(default_profile_path, self.default_profile)
+        with environment_append({"CONAN_DEFAULT_PROFILE_PATH": default_profile_path}):
+            unittest.TestCase.run(self, *args, **kwargs)
+
+    def setUp(self):
+        self.tmp_folder = temp_folder()
+        self.t = TestClient(base_folder=self.tmp_folder)
+
+        settings = ["os", "compiler", "build_type", "arch"]
+        if self.recipe_cppstd:
+            settings += ["cppstd"]
+
+        conanfile = textwrap.dedent("""
+            from conans import ConanFile
+            
+            class Lib(ConanFile):
+                settings = "{}"           
+            """.format('", "'.join(settings)))
+        self.t.save({"conanfile.py": conanfile})
+
+    def test_value_invalid(self):
+        self.t.run("create . hh/0.1@user/channel -shh:compiler=apple-clang -shh:compiler.cppstd=144",
+                   assert_error=True)
+        self.assertIn("Invalid setting '144' is not a valid 'settings.compiler.cppstd' value",
+                      self.t.out)
+
+    def test_value_different_with_scoped_setting(self):
+        self.t.run("create . hh/0.1@user/channel"
+                   " -s hh:cppstd=11"
+                   " -s hh:compiler=gcc"
+                   " -s hh:compiler.cppstd=14", assert_error=self.recipe_cppstd)
+        if self.recipe_cppstd:
+            self.assertIn("Package 'hh/0.1@user/channel': Do not use settings 'compiler.cppstd'"
+                          " together with 'cppstd'. Use only the former one.", self.t.out)
+        else:
+            # TODO: Settings are being constrained before checking...
+            pass
+
+    def test_value_different_with_general_setting(self):
+        deprecation_number = 1 if self.recipe_cppstd else 0
+        with catch_deprecation_warning(self, n=deprecation_number):
+            self.t.run("create . hh/0.1@user/channel"
+                       " -s cppstd=17"
+                       " -s hh:compiler=gcc"
+                       " -s hh:compiler.cppstd=14", assert_error=self.recipe_cppstd)
+        if self.recipe_cppstd:
+            self.assertIn("Package 'hh/0.1@user/channel': Do not use settings 'compiler.cppstd'"
+                          " together with 'cppstd'. Use only the former one.", self.t.out)
+
+    def test_conanfile_without_compiler(self):
+        conanfile = textwrap.dedent("""
+            from conans import ConanFile
+
+            class Lib(ConanFile):
+                settings = "os", "arch"
+        """)
+        t = TestClient(base_folder=temp_folder())
+        t.save({'conanfile.py': conanfile})
+
+        with catch_deprecation_warning(self):
+            # No mismatch, because settings for this conanfile does not include `compiler`
+            t.run("create . hh/0.1@user/channel"
+                  " -s cppstd=17"
+                  " -s hh:compiler=gcc"
+                  " -s hh:compiler.cppstd=14")
+            # TODO: Settings are being constrained before checking...
+
+    def test_conanfile_without_compiler_but_cppstd(self):
+        conanfile = textwrap.dedent("""
+            from conans import ConanFile
+
+            class Lib(ConanFile):
+                settings = "os", "arch", "cppstd"
+                
+                def configure(self):
+                    self.output.info(">>> cppstd: {}".format(self.settings.cppstd))
+        """)
+        t = TestClient(base_folder=temp_folder())
+        t.save({'conanfile.py': conanfile}, clean_first=True)
+
+        with catch_deprecation_warning(self, n=2):
+            # No mismatch, because settings for this conanfile does not include `compiler`
+            t.run("create . hh/0.1@user/channel"
+                  " -s cppstd=17"
+                  " -s hh:compiler=gcc"
+                  " -s hh:compiler.cppstd=14")
+        self.assertIn("Setting 'cppstd' is deprecated in favor of 'compiler.cppstd'", t.out)
+        self.assertIn(">>> cppstd: 17", t.out)
+        # TODO: Settings are being constrained before checking...
+
+
+class UseCompilerCppStdSettingTests(unittest.TestCase):
+
+    conanfile = textwrap.dedent("""
+        from conans import ConanFile
+        
+        class Lib(ConanFile):
+            settings = "cppstd", "os", "compiler", "arch", "build_type"
+            
+            def configure(self):
+                self.output.info(">>> cppstd: {}".format(self.settings.cppstd))
+                self.output.info(">>> compiler.cppstd: {}".format(self.settings.compiler.cppstd))
+    """)
+
+    def setUp(self):
+        self.t = TestClient()
+        self.t.save({'conanfile.py': self.conanfile})
+
+    def test_user_notice(self):
+        self.t.run("info .")
+        self.assertIn("Setting 'cppstd' is deprecated in favor of 'compiler.cppstd',"
+                      " please update your recipe.", self.t.out)
+
+    def test_only_cppstd(self):
+        with catch_deprecation_warning(self, n=2):
+            self.t.run("info . -s cppstd=14")
+        self.assertNotIn(">>> compiler.cppstd: 14", self.t.out)
+        self.assertIn(">>> cppstd: 14", self.t.out)
+        self.assertIn(">>> compiler.cppstd: None", self.t.out)
+
+    def test_only_compiler_cppstd(self):
+        """ settings.cppstd is available only if declared explicitly (otherwise it is deprecated) """
+        self.t.run("info . -s compiler.cppstd=14")
+        self.assertNotIn(">>> cppstd: 14", self.t.out)
+        self.assertIn(">>> cppstd: None", self.t.out)
+        self.assertIn(">>> compiler.cppstd: 14", self.t.out)
+
+    def test_both(self):
+        settings_str = "-s cppstd=14 -s compiler.cppstd=14"
+        self.t.run("info . {}".format(settings_str), assert_error=True)
+        self.assertIn("Do not use settings 'compiler.cppstd' together with 'cppstd'."
+                      " Use only the former one.", self.t.out)
+
diff --git a/conans/test/functional/cppstd/default_cppstd_test.py b/conans/test/functional/cppstd/default_cppstd_test.py
index a1188f46f05..b9cd086da0e 100644
--- a/conans/test/functional/cppstd/default_cppstd_test.py
+++ b/conans/test/functional/cppstd/default_cppstd_test.py
@@ -7,13 +7,12 @@
 
 from conans.client.build.cppstd_flags import cppstd_default
 from conans.client.tools import environment_append, save, load
+from conans.test.utils.deprecation import catch_deprecation_warning
 from conans.test.utils.test_files import temp_folder
 from conans.test.utils.tools import TestClient
 
 
 class DefaultCppTestCase(unittest.TestCase):
-    # Validate package ID computed taking into account different cppstd scenarios
-
     compiler = "gcc"
     compiler_version = "7"
 
@@ -34,14 +33,15 @@ class Library(ConanFile):
 
             def configure(self):
                 cppstd = self.settings.get_safe("cppstd")
+                compiler_cppstd = self.settings.get_safe("compiler.cppstd")
                 self.output.info(">>>> settings: {{}}".format(self.settings.fields))
                 self.output.info(">>>> cppstd: {{}}".format(cppstd))
+                self.output.info(">>>> compiler.cppstd: {{}}".format(compiler_cppstd))
         """)
 
     id_default = "d17189cfe7b11efbc5d701339a32d203745f8b81"
 
     def run(self, *args, **kwargs):
-        # Create and use a different default profile
         default_profile_path = os.path.join(temp_folder(), "default.profile")
         save(default_profile_path, self.default_profile)
         with environment_append({"CONAN_DEFAULT_PROFILE_PATH": default_profile_path}):
@@ -54,6 +54,7 @@ def setUp(self):
         self.assertEqual(target_id, self.id_default)
         self.assertIn(">>>> settings: ['compiler', 'os']", output)
         self.assertIn(">>>> cppstd: None", output)
+        self.assertIn(">>>> compiler.cppstd: gnu14", output)
 
     def _get_id(self, with_cppstd, settings_values=None):
         # Create the conanfile with corresponding settings
@@ -75,14 +76,22 @@ def _get_id(self, with_cppstd, settings_values=None):
         data = json.loads(load(json_file))
         self.assertEqual(len(data), 1)
 
-        # Return: ID, output
+        # Return ID, output
         return data[0]["id"], info_output
 
+
+class SettingsCppStdTests(DefaultCppTestCase):
+    """
+    Validate package ID computed taking into account different scenarios for 'cppstd'. The ID
+    should be the same if the setting is not provided and if it has the default value.
+    """
+
     def test_no_value(self):
         # No value passed for setting 'cppstd'
         id_with, output = self._get_id(with_cppstd=True)  # TODO: Should raise?
         self.assertIn(">>>> settings: ['compiler', 'cppstd', 'os']", output)
         self.assertIn(">>>> cppstd: None", output)
+        self.assertIn(">>>> compiler.cppstd: gnu14", output)
         self.assertEqual(self.id_default, id_with)
 
     def test_value_none(self):
@@ -90,20 +99,84 @@ def test_value_none(self):
         id_with, output = self._get_id(with_cppstd=True, settings_values={"cppstd": "None"})
         self.assertIn(">>>> settings: ['compiler', 'cppstd', 'os']", output)
         self.assertIn(">>>> cppstd: None", output)
+        self.assertIn(">>>> compiler.cppstd: gnu14", output)
         self.assertEqual(self.id_default, id_with)
 
     def test_value_default(self):
         # Explicit value (equals to default) passed to setting 'cppstd'
         cppstd = cppstd_default(self.compiler, self.compiler_version)
-        id_with, output = self._get_id(with_cppstd=True, settings_values={"cppstd": cppstd})
+        with catch_deprecation_warning(self, n=2):
+            id_with, output = self._get_id(with_cppstd=True, settings_values={"cppstd": cppstd})
         self.assertIn(">>>> settings: ['compiler', 'cppstd', 'os']", output)
         self.assertIn(">>>> cppstd: gnu14", output)
+        self.assertIn(">>>> compiler.cppstd: None", output)
         self.assertEqual(self.id_default, id_with)
 
-    def test_value_other(self):
+    def test_value_non_default(self):
         # Explicit value (not the default) passed to setting 'cppstd'
-        id_with, output = self._get_id(with_cppstd=True, settings_values={"cppstd": "14"})
+        with catch_deprecation_warning(self, n=2):
+            id_with, output = self._get_id(with_cppstd=True, settings_values={"cppstd": "14"})
         self.assertIn(">>>> settings: ['compiler', 'cppstd', 'os']", output)
         self.assertIn(">>>> cppstd: 14", output)
+        self.assertIn(">>>> compiler.cppstd: None", output)
         self.assertNotEqual(self.id_default, id_with)
 
+
+class SettingsCompilerCppStdTests(DefaultCppTestCase):
+    """
+    Validate package ID computed taking into account different scenarios for 'compiler.cppstd'. The
+    ID has to be the same if the setting is not informed and if it has the default value, also
+    these values should be the same as the ones using the 'cppstd' approach.
+    """
+
+    def _get_id(self, with_cppstd=False, settings_values=None):
+        assert not with_cppstd
+        return super(SettingsCompilerCppStdTests, self)._get_id(with_cppstd=False,
+                                                                settings_values=settings_values)
+
+    def test_value_none(self):
+        # Explicit value 'None' passed to setting 'cppstd'
+        id_with, output = self._get_id(settings_values={"compiler.cppstd": "None"})
+        self.assertIn(">>>> settings: ['compiler', 'os']", output)
+        self.assertIn(">>>> cppstd: None", output)
+        self.assertIn(">>>> compiler.cppstd: gnu14", output)
+        self.assertEqual(self.id_default, id_with)
+
+    def test_value_default(self):
+        # Explicit value (equals to default) passed to setting 'compiler.cppstd'
+        cppstd = cppstd_default(self.compiler, self.compiler_version)
+        id_with, output = self._get_id(settings_values={"compiler.cppstd": cppstd})
+        self.assertIn(">>>> settings: ['compiler', 'os']", output)
+        self.assertIn(">>>> cppstd: None", output)
+        self.assertIn(">>>> compiler.cppstd: gnu14", output)
+        self.assertEqual(self.id_default, id_with)
+
+    def test_value_other(self):
+        # Explicit value (not the default) passed to setting 'cppstd'
+        id_with, output = self._get_id(settings_values={"compiler.cppstd": "14"})
+        self.assertIn(">>>> settings: ['compiler', 'os']", output)
+        self.assertIn(">>>> cppstd: None", output)
+        self.assertIn(">>>> compiler.cppstd: 14", output)
+        self.assertNotEqual(self.id_default, id_with)
+
+
+class SettingsCompareCppStdApproaches(DefaultCppTestCase):
+    """
+    Check scenario using 'cppstd' and 'compiler.cppstd', if those are given the same value
+    (but different from the default one) then the ID for the packages is not required to be
+    the same.
+    """
+
+    def test_cppstd_non_defaults(self):
+        cppstd_value = "14"  # Not the default
+        with catch_deprecation_warning(self, n=2):
+            id_with_old, _ = self._get_id(with_cppstd=True, settings_values={"cppstd": cppstd_value})
+        id_with_new, _ = self._get_id(with_cppstd=False,
+                                      settings_values={'compiler.cppstd': cppstd_value})
+
+        # Those are different from the target one (ID using default value or None)
+        self.assertNotEqual(self.id_default, id_with_old)
+        self.assertNotEqual(self.id_default, id_with_new)
+
+        # They are different between them
+        self.assertNotEqual(id_with_new, id_with_old)
diff --git a/conans/test/integration/cppstd_test.py b/conans/test/integration/cppstd_test.py
index a0f63efda2b..8f5e1a88c6e 100644
--- a/conans/test/integration/cppstd_test.py
+++ b/conans/test/integration/cppstd_test.py
@@ -1,6 +1,7 @@
 import unittest
 
 from conans.paths import CONANFILE
+from conans.test.utils.deprecation import catch_deprecation_warning
 from conans.test.utils.tools import TestClient
 
 
@@ -18,15 +19,17 @@ class TestConan(ConanFile):
 
 """
         client.save({CONANFILE: conanfile})
-        client.run('create . user/testing -s compiler="gcc" '
-                   '-s compiler.libcxx="libstdc++11" '
-                   '-s compiler.version="4.6" -s cppstd=17', assert_error=True)
+        with catch_deprecation_warning(self):
+            client.run('create . user/testing -s compiler="gcc" '
+                       '-s compiler.libcxx="libstdc++11" '
+                       '-s compiler.version="4.6" -s cppstd=17', assert_error=True)
 
         self.assertIn("The specified 'cppstd=17' is not available for 'gcc 4.6'", client.out)
         self.assertIn("Possible values are ['11', '98', 'gnu11', 'gnu98']", client.out)
 
-        client.run('create . user/testing -s compiler="gcc" -s compiler.libcxx="libstdc++11" '
-                   '-s compiler.version="6.3" -s cppstd=17')
+        with catch_deprecation_warning(self, n=2):
+            client.run('create . user/testing -s compiler="gcc" -s compiler.libcxx="libstdc++11" '
+                       '-s compiler.version="6.3" -s cppstd=17')
 
     def gcc_8_std_20_test(self):
         client = TestClient()
@@ -40,9 +43,10 @@ class TestConan(ConanFile):
 
 """
         client.save({CONANFILE: conanfile})
-        client.run('create . user/testing -s compiler="gcc" '
-                   '-s compiler.libcxx="libstdc++11" '
-                   '-s compiler.version="8" -s cppstd=20')
+        with catch_deprecation_warning(self, n=2):
+            client.run('create . user/testing -s compiler="gcc" '
+                       '-s compiler.libcxx="libstdc++11" '
+                       '-s compiler.version="8" -s cppstd=20')
 
     def set_default_package_id_test(self):
         client = TestClient()
@@ -56,7 +60,8 @@ class TestConan(ConanFile):
     def build(self):
         self.output.warn("BUILDING!")
 """
-        client.save({CONANFILE: conanfile % ""})  # Without the setting
+        # Without the setting
+        client.save({CONANFILE: conanfile % ""})
         client.run('create . user/testing -s compiler="gcc" -s compiler.version="7.1" '
                    '-s compiler.libcxx="libstdc++" '
                    '--build missing')
@@ -64,10 +69,12 @@ def build(self):
 
         # Add the setting but with the default value, should not build again
         client.save({CONANFILE: conanfile % '"cppstd"'})  # With the setting
-        client.run('create . user/testing -s compiler="gcc" -s compiler.version="7.1" '
-                   '-s compiler.libcxx="libstdc++" '
-                   '-s cppstd=gnu14 '
-                   '--build missing')
+        with catch_deprecation_warning(self, n=2):
+            client.run('create . user/testing -s compiler="gcc" -s compiler.version="7.1" '
+                       '-s compiler.libcxx="libstdc++" '
+                       '-s cppstd=gnu14 '
+                       '--build missing')
+
         if client.cache.config.revisions_enabled:
             self.assertIn("doesn't belong to the installed recipe revision, removing folder",
                           client.out)
@@ -77,8 +84,9 @@ def build(self):
 
         # Add the setting but with a non-default value, should build again
         client.save({CONANFILE: conanfile % '"cppstd"'})  # With the setting
-        client.run('create . user/testing -s compiler="gcc" -s compiler.version="7.1" '
-                   '-s compiler.libcxx="libstdc++" '
-                   '-s cppstd=gnu17 '
-                   '--build missing')
+        with catch_deprecation_warning(self, n=2):
+            client.run('create . user/testing -s compiler="gcc" -s compiler.version="7.1" '
+                       '-s compiler.libcxx="libstdc++" '
+                       '-s cppstd=gnu17 '
+                       '--build missing')
         self.assertIn("BUILDING!", client.out)
diff --git a/conans/test/integration/package_id_test.py b/conans/test/integration/package_id_test.py
index 95e84293fb8..b6b261486ff 100644
--- a/conans/test/integration/package_id_test.py
+++ b/conans/test/integration/package_id_test.py
@@ -5,6 +5,7 @@
 from conans.model.ref import ConanFileReference, PackageReference
 from conans.paths import CONANINFO
 from conans.test.utils.conanfile import TestConanFile
+from conans.test.utils.deprecation import catch_deprecation_warning
 from conans.test.utils.tools import TestClient
 from conans.util.env_reader import get_env
 from conans.util.files import load
@@ -37,7 +38,6 @@ def _export(self, name, version, package_id_text=None, requires=None,
                                   default_options=[("an_option", "%s" % default_option_value)],
                                   package_id=package_id_text,
                                   settings=settings)
-
         self.client.save({"conanfile.py": str(conanfile)}, clean_first=True)
         revisions_enabled = self.client.cache.config.revisions_enabled
         self.client.disable_revisions()
@@ -369,15 +369,21 @@ def test_standard_version_default_matching(self):
                      channel="user/testing",
                      settings='"compiler", "cppstd"')
 
-        self.client.run('install Hello/1.2.0@user/testing '
-                        ' -s compiler="gcc" -s compiler.libcxx=libstdc++11'
-                        ' -s compiler.version=7.2 -s cppstd=gnu14')  # Default, already built
+        with catch_deprecation_warning(self, n=2):
+            self.client.run('info Hello/1.2.0@user/testing  -s compiler="gcc" '
+                            '-s compiler.libcxx=libstdc++11  -s compiler.version=7.2 '
+                            '-s cppstd=gnu14')
+        with catch_deprecation_warning(self, n=2):
+            self.client.run('install Hello/1.2.0@user/testing'
+                            ' -s compiler="gcc" -s compiler.libcxx=libstdc++11'
+                            ' -s compiler.version=7.2 -s cppstd=gnu14')  # Default, already built
 
         # Should NOT have binary available
-        self.client.run('install Hello/1.2.0@user/testing'
-                        ' -s compiler="gcc" -s compiler.libcxx=libstdc++11'
-                        ' -s compiler.version=7.2 -s cppstd=gnu11',
-                        assert_error=True)
+        with catch_deprecation_warning(self, n=2):
+            self.client.run('install Hello/1.2.0@user/testing'
+                            ' -s compiler="gcc" -s compiler.libcxx=libstdc++11'
+                            ' -s compiler.version=7.2 -s cppstd=gnu11',
+                            assert_error=True)
 
         self.assertIn("Missing prebuilt package for 'Hello/1.2.0@user/testing'", self.client.out)
 
@@ -394,8 +400,9 @@ def test_standard_version_default_non_matching(self):
                      channel="user/testing",
                      settings='"compiler", "cppstd"'
                      )
-        self.client.run('install Hello/1.2.0@user/testing '
-                                ' -s compiler="gcc" -s compiler.libcxx=libstdc++11'
-                                ' -s compiler.version=7.2 -s cppstd=gnu14',
-                                assert_error=True)  # Default
+        with catch_deprecation_warning(self, n=2):
+            self.client.run('install Hello/1.2.0@user/testing'
+                            ' -s compiler="gcc" -s compiler.libcxx=libstdc++11'
+                            ' -s compiler.version=7.2 -s cppstd=gnu14',
+                            assert_error=True)  # Default
         self.assertIn("Missing prebuilt package for 'Hello/1.2.0@user/testing'", self.client.out)
diff --git a/conans/test/unittests/client/generators/cmake_paths_test.py b/conans/test/unittests/client/generators/cmake_paths_test.py
index d8c0c7763c7..41fba6e1e6c 100644
--- a/conans/test/unittests/client/generators/cmake_paths_test.py
+++ b/conans/test/unittests/client/generators/cmake_paths_test.py
@@ -8,13 +8,36 @@
 from conans.model.env_info import EnvValues
 from conans.test.utils.test_files import temp_folder
 from conans.test.utils.tools import TestBufferConanOutput
+from conans.errors import ConanException
+
+
+class _MockSettings(object):
+    build_type = None
+    os = None
+    os_build = None
+    fields = []
+
+    def __init__(self, build_type=None):
+        self.build_type = build_type
+
+    @property
+    def compiler(self):
+        raise ConanException("mock: not available")
+
+    def constraint(self, _):
+        return self
+
+    def get_safe(self, _):
+        return None
+
+    def items(self):
+        return {}
 
 
 class CMakePathsGeneratorTest(unittest.TestCase):
 
     def cmake_vars_unit_test(self):
-        settings_mock = namedtuple("Settings", "build_type, os, os_build, constraint")
-        settings = settings_mock("Release", None, None, lambda x: x)
+        settings = _MockSettings("Release")
         conanfile = ConanFile(TestBufferConanOutput(), None)
         conanfile.initialize(settings, EnvValues())
         tmp = temp_folder()
diff --git a/conans/test/unittests/client/generators/cmake_test.py b/conans/test/unittests/client/generators/cmake_test.py
index 6490df612fd..98fc66c8cf3 100644
--- a/conans/test/unittests/client/generators/cmake_test.py
+++ b/conans/test/unittests/client/generators/cmake_test.py
@@ -1,11 +1,11 @@
 import os
 import re
 import unittest
-from collections import namedtuple
 
 from conans.client.conf import default_settings_yml
 from conans.client.generators.cmake import CMakeGenerator
 from conans.client.generators.cmake_multi import CMakeMultiGenerator
+from conans.errors import ConanException
 from conans.model.build_info import CppInfo
 from conans.model.conan_file import ConanFile
 from conans.model.env_info import EnvValues
@@ -16,6 +16,29 @@
 from conans.util.files import save
 
 
+class _MockSettings(object):
+    build_type = None
+    os = None
+    os_build = None
+    fields = []
+
+    def __init__(self, build_type=None):
+        self.build_type = build_type
+
+    @property
+    def compiler(self):
+        raise ConanException("mock: not available")
+
+    def constraint(self, _):
+        return self
+
+    def get_safe(self, _):
+        return None
+
+    def items(self):
+        return {}
+
+
 class CMakeGeneratorTest(unittest.TestCase):
 
     def _extract_macro(self, name, text):
@@ -49,10 +72,9 @@ def variables_setup_test(self):
         self.assertIn('set(CONAN_USER_LIB2_MYVAR2 "myvalue4")', cmake_lines)
 
     def paths_cmake_multi_user_vars_test(self):
-        settings_mock = namedtuple("Settings", "build_type, os, os_build, constraint")
+        settings_mock = _MockSettings(build_type="Release")
         conanfile = ConanFile(TestBufferConanOutput(), None)
-        conanfile.initialize(settings_mock("Release", None, None,
-                                           lambda x: x), EnvValues())
+        conanfile.initialize(settings_mock, EnvValues())
         ref = ConanFileReference.loads("MyPkg/0.1@lasote/stables")
         tmp_folder = temp_folder()
         save(os.path.join(tmp_folder, "lib", "mylib.lib"), "")
@@ -69,10 +91,9 @@ def paths_cmake_multi_user_vars_test(self):
         self.assertIn('set(CONAN_LIB_DIRS_MYPKG_RELEASE "root_folder/lib")', cmake_lines)
 
     def paths_cmake_test(self):
-        settings_mock = namedtuple("Settings", "build_type, os, os_build, constraint, items")
+        settings_mock = _MockSettings()
         conanfile = ConanFile(TestBufferConanOutput(), None)
-        conanfile.initialize(settings_mock(None, None, None, lambda x: x,
-                                           lambda: {}), EnvValues())
+        conanfile.initialize(settings_mock, EnvValues())
         ref = ConanFileReference.loads("MyPkg/0.1@lasote/stables")
         tmp_folder = temp_folder()
         save(os.path.join(tmp_folder, "lib", "mylib.lib"), "")
@@ -89,10 +110,9 @@ def paths_cmake_test(self):
         self.assertIn('set(CONAN_LIB_DIRS_MYPKG_RELEASE "root_folder/lib")', cmake_lines)
 
     def variables_cmake_multi_user_vars_test(self):
-        settings_mock = namedtuple("Settings", "build_type, os, os_build, constraint")
+        settings_mock = _MockSettings(build_type="Release")
         conanfile = ConanFile(TestBufferConanOutput(), None)
-        conanfile.initialize(settings_mock("Release", None, None, lambda x: x,),
-                             EnvValues())
+        conanfile.initialize(settings_mock, EnvValues())
         conanfile.deps_user_info["LIB1"].myvar = "myvalue"
         conanfile.deps_user_info["LIB1"].myvar2 = "myvalue2"
         conanfile.deps_user_info["lib2"].MYVAR2 = "myvalue4"
@@ -104,10 +124,9 @@ def variables_cmake_multi_user_vars_test(self):
         self.assertIn('set(CONAN_USER_LIB2_MYVAR2 "myvalue4")', cmake_lines)
 
     def variables_cmake_multi_user_vars_escape_test(self):
-        settings_mock = namedtuple("Settings", "build_type, os, os_build, constraint")
+        settings_mock = _MockSettings(build_type="Release")
         conanfile = ConanFile(TestBufferConanOutput(), None)
-        conanfile.initialize(settings_mock("Release", None, None, lambda x: x,),
-                             EnvValues())
+        conanfile.initialize(settings_mock, EnvValues())
         conanfile.deps_user_info["FOO"].myvar = 'my"value"'
         conanfile.deps_user_info["FOO"].myvar2 = 'my${value}'
         conanfile.deps_user_info["FOO"].myvar3 = 'my\\value'
diff --git a/conans/test/unittests/client/profile_loader/__init__.py b/conans/test/unittests/client/profile_loader/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/conans/test/unittests/client/profile_loader/compiler_cppstd_test.py b/conans/test/unittests/client/profile_loader/compiler_cppstd_test.py
new file mode 100644
index 00000000000..8781e23d68e
--- /dev/null
+++ b/conans/test/unittests/client/profile_loader/compiler_cppstd_test.py
@@ -0,0 +1,113 @@
+# coding=utf-8
+
+import os
+import textwrap
+import unittest
+
+import six
+from jinja2 import Template
+
+from conans.client.cache.cache import ClientCache
+from conans.client.profile_loader import profile_from_args
+from conans.errors import ConanException
+from conans.test.utils.deprecation import catch_deprecation_warning
+from conans.test.utils.test_files import temp_folder
+from conans.test.utils.tools import TestBufferConanOutput
+from conans.util.files import save
+
+
+class SettingsCppStdTests(unittest.TestCase):
+
+    def setUp(self):
+        self.tmp_folder = temp_folder()
+        self.cache = ClientCache(self.tmp_folder, TestBufferConanOutput())
+
+    def _save_profile(self, cppstd=None, compiler_cppstd=None, filename="default"):
+        fullpath = os.path.join(self.cache.profiles_path, filename)
+
+        t = Template(textwrap.dedent("""
+            [settings]
+            os=Macos
+            arch=x86_64
+            compiler=apple-clang
+            {% if compiler_cppstd %}compiler.cppstd={{ compiler_cppstd }}{% endif %}
+            compiler.libcxx=libc++
+            compiler.version=10.0
+            {% if cppstd %}cppstd={{ cppstd }}{% endif %}
+            """))
+
+        save(fullpath, t.render(cppstd=cppstd, compiler_cppstd=compiler_cppstd))
+        return filename
+
+    def test_no_value(self):
+        self._save_profile()
+
+        r = profile_from_args(["default", ], [], [], [], cwd=self.tmp_folder, cache=self.cache)
+        r.process_settings(self.cache)
+        self.assertIn("compiler.cppstd", r.settings)
+        self.assertNotIn("cppstd", r.settings)
+
+    def test_value_none(self):
+        self._save_profile(compiler_cppstd="None")
+
+        r = profile_from_args(["default", ], [], [], [], cwd=self.tmp_folder, cache=self.cache)
+        r.process_settings(self.cache)
+        self.assertEqual(r.settings["compiler.cppstd"], "gnu98")
+        self.assertNotIn("cppstd", r.settings)
+
+    def test_value_valid(self):
+        self._save_profile(compiler_cppstd="11")
+
+        r = profile_from_args(["default", ], [], [], [], cwd=self.tmp_folder, cache=self.cache)
+        r.process_settings(self.cache)
+        self.assertEqual(r.settings["compiler.cppstd"], "11")
+        self.assertNotIn("cppstd", r.settings)
+
+    def test_value_invalid(self):
+        self._save_profile(compiler_cppstd="13")
+
+        r = profile_from_args(["default", ], [], [], [], cwd=self.tmp_folder, cache=self.cache)
+        with six.assertRaisesRegex(self, ConanException, "Invalid setting '13' is not a valid "
+                                                         "'settings.compiler.cppstd' value"):
+            r.process_settings(self.cache)
+        self.assertNotIn("cppstd", r.settings)
+
+    def test_value_duplicated_None(self):
+        self._save_profile(compiler_cppstd="None", cppstd="None")
+
+        r = profile_from_args(["default", ], [], [], [], cwd=self.tmp_folder, cache=self.cache)
+        r.process_settings(self.cache)
+        self.assertEqual(r.settings["compiler.cppstd"], "gnu98")
+        self.assertEqual(r.settings["cppstd"], "None")
+
+    def test_value_duplicated(self):
+        self._save_profile(compiler_cppstd="11", cppstd="11")
+
+        r = profile_from_args(["default", ], [], [], [], cwd=self.tmp_folder, cache=self.cache)
+        with six.assertRaisesRegex(self, ConanException, "Do not use settings 'compiler.cppstd'"
+                                                         " together with 'cppstd'. Use only the"
+                                                         " former one."):
+            with catch_deprecation_warning(self):
+                r.process_settings(self.cache)
+        self.assertEqual(r.settings["compiler.cppstd"], "11")
+        self.assertEqual(r.settings["cppstd"], "11")
+
+    def test_value_different(self):
+        self._save_profile(cppstd="14", compiler_cppstd="11")
+
+        r = profile_from_args(["default", ], [], [], [], cwd=self.tmp_folder, cache=self.cache)
+        with six.assertRaisesRegex(self, ConanException, "Do not use settings 'compiler.cppstd'"
+                                                         " together with 'cppstd'. Use only the"
+                                                         " former one"):
+            with catch_deprecation_warning(self):
+                r.process_settings(self.cache)
+
+    def test_value_from_cppstd(self):
+        self._save_profile(cppstd="11")
+
+        r = profile_from_args(["default", ], [], [], [], cwd=self.tmp_folder, cache=self.cache)
+        with catch_deprecation_warning(self):
+            r.process_settings(self.cache)
+        self.assertNotIn('compiler.cppstd', r.settings)
+        self.assertEqual(r.settings["cppstd"], "11")
+
diff --git a/conans/test/unittests/client/profile_loader_test.py b/conans/test/unittests/client/profile_loader/profile_loader_test.py
similarity index 100%
rename from conans/test/unittests/client/profile_loader_test.py
rename to conans/test/unittests/client/profile_loader/profile_loader_test.py
diff --git a/conans/test/unittests/model/other_settings_test.py b/conans/test/unittests/model/other_settings_test.py
index a77ee1fb21e..8c220371240 100644
--- a/conans/test/unittests/model/other_settings_test.py
+++ b/conans/test/unittests/model/other_settings_test.py
@@ -5,6 +5,7 @@
 from conans.model.ref import PackageReference
 from conans.model.settings import bad_value_msg, undefined_value
 from conans.paths import CONANFILE, CONANINFO
+from conans.test.utils.deprecation import catch_deprecation_warning
 from conans.test.utils.tools import TestClient
 from conans.util.files import load, save
 
@@ -46,7 +47,8 @@ class Pkg(ConanFile):
     settings = "compiler", "cppstd"
 """
         client.save({"conanfile.py": conanfile})
-        client.run("create . Pkg/0.1@lasote/testing")
+        with catch_deprecation_warning(self, n=2):
+            client.run("create . Pkg/0.1@lasote/testing")
         self.assertIn("""Configuration:
 [settings]
 compiler=mycomp
diff --git a/conans/test/utils/deprecation.py b/conans/test/utils/deprecation.py
new file mode 100644
index 00000000000..36d1b0bc6cc
--- /dev/null
+++ b/conans/test/utils/deprecation.py
@@ -0,0 +1,14 @@
+# coding=utf-8
+
+import warnings
+from contextlib import contextmanager
+
+
+@contextmanager
+def catch_deprecation_warning(test_suite, n=1):
+    with warnings.catch_warnings(record=True) as w:
+        warnings.filterwarnings("always", module="(.*\.)?conans\..*")
+        yield
+        if n:
+            test_suite.assertEqual(len(w), n)
+            test_suite.assertTrue(issubclass(w[0].category, UserWarning))


Code review comments:
diff_hunk: @@ -398,14 +404,31 @@ def default_std_matching(self):
         same as specifying None, packages are the same
         """
 
-        if self.full_settings.cppstd and \
-                self.full_settings.compiler and \
-                self.full_settings.compiler.version:
+        if self.full_settings.compiler and \
+            self.full_settings.compiler.version:
             default = cppstd_default(str(self.full_settings.compiler),
                                      str(self.full_settings.compiler.version))
-            if default == str(self.full_settings.cppstd):
+
+            if str(self.full_settings.cppstd) == default:
                 self.settings.cppstd = None
 
+            if str(self.full_settings.compiler.cppstd) == default:
+                def remove_cppstd(settings):
+                    try:
+                        settings.compiler.cppstd = None  # It's the default, assign None
+                    except AttributeError:
+                        # Settings can be different at the moment of executing this function
+                        pass
+                    finally:
+                        return settings
+                self._adjust_settings_for_std = remove_cppstd
+
     def default_std_non_matching(self):
         if self.full_settings.cppstd:
             self.settings.cppstd = self.full_settings.cppstd
+        self._adjust_settings_for_std = lambda u: u  # Do nothing
+
+    def _settings_sha(self):
+        settings = self.settings.copy()
path: conans/model/info.py
text: @user1:
I suggest:
- Having a default adjust = None
- If adjust = None, no need to copy, no need to call it.
- The adjust would do the copy internally, and return the sha. In this way it is not possible to forget to copy, which would destroy the ``info`` original values.

@author:
I agree with the `adjust=None`, but I prefer to leave the `copy` and `sha` outside the _adjust_ function, maybe we can add here more _adjustments_ only for computing the ID and this way we can create the pipeline (I'm not sure if it will make sense right now for the `build_settings` and `toolset` ones):

```python
    def _settings_sha(self):
        if self._adjust_settings_for_std:
            settings = self.settings.copy()
            settings = self._adjust_settings_for_std(settings)
            settings = self._adjust_other_thing(settings)
            settings = self._adjust_...(settings)
            settings = ....
            return settings.sha
        return self.settings.sha
```

Just tell me if you really think it is better to make the copy inside the function and return the sha.